Datum each(PG_FUNCTION_ARGS) { FuncCallContext *funcctx; AKStore *st; if (SRF_IS_FIRSTCALL()) { TupleDesc tupdesc; MemoryContext oldcontext; HStore *hs = PG_GETARG_HS(0); funcctx = SRF_FIRSTCALL_INIT(); oldcontext = MemoryContextSwitchTo(funcctx->multi_call_memory_ctx); st = (AKStore *) palloc(sizeof(AKStore)); st->i = 0; st->hs = (HStore *) palloc(VARSIZE(hs)); memcpy(st->hs, hs, VARSIZE(hs)); funcctx->user_fctx = (void *) st; /* Build a tuple descriptor for our result type */ if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE) elog(ERROR, "return type must be a row type"); funcctx->attinmeta = TupleDescGetAttInMetadata(tupdesc); MemoryContextSwitchTo(oldcontext); PG_FREE_IF_COPY(hs, 0); } funcctx = SRF_PERCALL_SETUP(); st = (AKStore *) funcctx->user_fctx; if (st->i < st->hs->size) { HEntry *ptr = &(ARRPTR(st->hs)[st->i]); Datum res, dvalues[2]; char nulls[] = {' ', ' '}; text *item; HeapTuple tuple; item = (text *) palloc(VARHDRSZ + ptr->keylen); SET_VARSIZE(item, VARHDRSZ + ptr->keylen); memcpy(VARDATA(item), STRPTR(st->hs) + ptr->pos, ptr->keylen); dvalues[0] = PointerGetDatum(item); if (ptr->valisnull) { dvalues[1] = (Datum) 0; nulls[1] = 'n'; } else { int vallen = ptr->vallen; item = (text *) palloc(VARHDRSZ + vallen); SET_VARSIZE(item, VARHDRSZ + vallen); memcpy(VARDATA(item), STRPTR(st->hs) + ptr->pos + ptr->keylen, vallen); dvalues[1] = PointerGetDatum(item); } st->i++; tuple = heap_formtuple(funcctx->attinmeta->tupdesc, dvalues, nulls); res = HeapTupleGetDatum(tuple); pfree(DatumGetPointer(dvalues[0])); if (nulls[1] != 'n') pfree(DatumGetPointer(dvalues[1])); SRF_RETURN_NEXT(funcctx, res); } pfree(st->hs); pfree(st); SRF_RETURN_DONE(funcctx); }
/* ---------------- * printtup_internal_20 --- print a binary tuple in protocol 2.0 * * We use a different message type, i.e. 'B' instead of 'D' to * indicate a tuple in internal (binary) form. * * This is largely same as printtup_20, except we use binary formatting. * ---------------- */ static bool printtup_internal_20(TupleTableSlot *slot, DestReceiver *self) { TupleDesc typeinfo = slot->tts_tupleDescriptor; DR_printtup *myState = (DR_printtup *) self; MemoryContext oldcontext; StringInfo buf = &myState->buf; int natts = typeinfo->natts; int i, j, k; /* Set or update my derived attribute info, if needed */ if (myState->attrinfo != typeinfo || myState->nattrs != natts) printtup_prepare_info(myState, typeinfo, natts); /* Make sure the tuple is fully deconstructed */ slot_getallattrs(slot); /* Switch into per-row context so we can recover memory below */ oldcontext = MemoryContextSwitchTo(myState->tmpcontext); /* * tell the frontend to expect new tuple data (in binary style) */ pq_beginmessage_reuse(buf, 'B'); /* * send a bitmap of which attributes are not null */ j = 0; k = 1 << 7; for (i = 0; i < natts; ++i) { if (!slot->tts_isnull[i]) j |= k; /* set bit if not null */ k >>= 1; if (k == 0) /* end of byte? */ { pq_sendint8(buf, j); j = 0; k = 1 << 7; } } if (k != (1 << 7)) /* flush last partial byte */ pq_sendint8(buf, j); /* * send the attributes of this tuple */ for (i = 0; i < natts; ++i) { PrinttupAttrInfo *thisState = myState->myinfo + i; Datum attr = slot->tts_values[i]; bytea *outputbytes; if (slot->tts_isnull[i]) continue; Assert(thisState->format == 1); outputbytes = SendFunctionCall(&thisState->finfo, attr); pq_sendint32(buf, VARSIZE(outputbytes) - VARHDRSZ); pq_sendbytes(buf, VARDATA(outputbytes), VARSIZE(outputbytes) - VARHDRSZ); } pq_endmessage_reuse(buf); /* Return to caller's context, and flush row's temporary memory */ MemoryContextSwitchTo(oldcontext); MemoryContextReset(myState->tmpcontext); return true; }
Datum heap_page_items(PG_FUNCTION_ARGS) { bytea *raw_page = PG_GETARG_BYTEA_P(0); heap_page_items_state *inter_call_data = NULL; FuncCallContext *fctx; int raw_page_size; if (!superuser()) ereport(ERROR, (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), (errmsg("must be superuser to use raw page functions")))); raw_page_size = VARSIZE(raw_page) - VARHDRSZ; if (SRF_IS_FIRSTCALL()) { TupleDesc tupdesc; MemoryContext mctx; if (raw_page_size < SizeOfPageHeaderData) ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), errmsg("input page too small (%d bytes)", raw_page_size))); fctx = SRF_FIRSTCALL_INIT(); mctx = MemoryContextSwitchTo(fctx->multi_call_memory_ctx); inter_call_data = palloc(sizeof(heap_page_items_state)); /* Build a tuple descriptor for our result type */ if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE) elog(ERROR, "return type must be a row type"); inter_call_data->tupd = tupdesc; inter_call_data->offset = FirstOffsetNumber; inter_call_data->page = VARDATA(raw_page); fctx->max_calls = PageGetMaxOffsetNumber(inter_call_data->page); fctx->user_fctx = inter_call_data; MemoryContextSwitchTo(mctx); } fctx = SRF_PERCALL_SETUP(); inter_call_data = fctx->user_fctx; if (fctx->call_cntr < fctx->max_calls) { Page page = inter_call_data->page; HeapTuple resultTuple; Datum result; ItemId id; Datum values[13]; bool nulls[13]; uint16 lp_offset; uint16 lp_flags; uint16 lp_len; memset(nulls, 0, sizeof(nulls)); /* Extract information from the line pointer */ id = PageGetItemId(page, inter_call_data->offset); lp_offset = ItemIdGetOffset(id); lp_flags = ItemIdGetFlags(id); lp_len = ItemIdGetLength(id); values[0] = UInt16GetDatum(inter_call_data->offset); values[1] = UInt16GetDatum(lp_offset); values[2] = UInt16GetDatum(lp_flags); values[3] = UInt16GetDatum(lp_len); /* * We do just enough validity checking to make sure we don't reference * data outside the page passed to us. The page could be corrupt in * many other ways, but at least we won't crash. */ if (ItemIdHasStorage(id) && lp_len >= sizeof(HeapTupleHeader) && lp_offset == MAXALIGN(lp_offset) && lp_offset + lp_len <= raw_page_size) { HeapTupleHeader tuphdr; int bits_len; /* Extract information from the tuple header */ tuphdr = (HeapTupleHeader) PageGetItem(page, id); values[4] = UInt32GetDatum(HeapTupleHeaderGetXmin(tuphdr)); values[5] = UInt32GetDatum(HeapTupleHeaderGetRawXmax(tuphdr)); values[6] = UInt32GetDatum(HeapTupleHeaderGetRawCommandId(tuphdr)); /* shared with xvac */ values[7] = PointerGetDatum(&tuphdr->t_ctid); values[8] = UInt32GetDatum(tuphdr->t_infomask2); values[9] = UInt32GetDatum(tuphdr->t_infomask); values[10] = UInt8GetDatum(tuphdr->t_hoff); /* * We already checked that the item as is completely within the * raw page passed to us, with the length given in the line * pointer.. Let's check that t_hoff doesn't point over lp_len, * before using it to access t_bits and oid. */ if (tuphdr->t_hoff >= sizeof(HeapTupleHeader) && tuphdr->t_hoff <= lp_len) { if (tuphdr->t_infomask & HEAP_HASNULL) { bits_len = tuphdr->t_hoff - (((char *) tuphdr->t_bits) -((char *) tuphdr)); values[11] = CStringGetTextDatum( bits_to_text(tuphdr->t_bits, bits_len * 8)); } else nulls[11] = true; if (tuphdr->t_infomask & HEAP_HASOID) values[12] = HeapTupleHeaderGetOid(tuphdr); else nulls[12] = true; } else { nulls[11] = true; nulls[12] = true; } } else { /* * The line pointer is not used, or it's invalid. Set the rest of * the fields to NULL */ int i; for (i = 4; i <= 12; i++) nulls[i] = true; } /* Build and return the result tuple. */ resultTuple = heap_form_tuple(inter_call_data->tupd, values, nulls); result = HeapTupleGetDatum(resultTuple); inter_call_data->offset++; SRF_RETURN_NEXT(fctx, result); } else SRF_RETURN_DONE(fctx); }
/* ---------------- * index_form_tuple * * This shouldn't leak any memory; otherwise, callers such as * tuplesort_putindextuplevalues() will be very unhappy. * ---------------- */ IndexTuple index_form_tuple(TupleDesc tupleDescriptor, Datum *values, bool *isnull) { char *tp; /* tuple pointer */ IndexTuple tuple; /* return tuple */ Size size, data_size, hoff; int i; unsigned short infomask = 0; bool hasnull = false; uint16 tupmask = 0; int numberOfAttributes = tupleDescriptor->natts; #ifdef TOAST_INDEX_HACK Datum untoasted_values[INDEX_MAX_KEYS]; bool untoasted_free[INDEX_MAX_KEYS]; #endif if (numberOfAttributes > INDEX_MAX_KEYS) ereport(ERROR, (errcode(ERRCODE_TOO_MANY_COLUMNS), errmsg("number of index columns (%d) exceeds limit (%d)", numberOfAttributes, INDEX_MAX_KEYS))); #ifdef TOAST_INDEX_HACK for (i = 0; i < numberOfAttributes; i++) { Form_pg_attribute att = tupleDescriptor->attrs[i]; untoasted_values[i] = values[i]; untoasted_free[i] = false; /* Do nothing if value is NULL or not of varlena type */ if (isnull[i] || att->attlen != -1) continue; /* * If value is stored EXTERNAL, must fetch it so we are not depending * on outside storage. This should be improved someday. */ if (VARATT_IS_EXTERNAL(DatumGetPointer(values[i]))) { untoasted_values[i] = PointerGetDatum(heap_tuple_fetch_attr((struct varlena *) DatumGetPointer(values[i]))); untoasted_free[i] = true; } /* * If value is above size target, and is of a compressible datatype, * try to compress it in-line. */ if (!VARATT_IS_EXTENDED(DatumGetPointer(untoasted_values[i])) && VARSIZE(DatumGetPointer(untoasted_values[i])) > TOAST_INDEX_TARGET && (att->attstorage == 'x' || att->attstorage == 'm')) { Datum cvalue = toast_compress_datum(untoasted_values[i]); if (DatumGetPointer(cvalue) != NULL) { /* successful compression */ if (untoasted_free[i]) pfree(DatumGetPointer(untoasted_values[i])); untoasted_values[i] = cvalue; untoasted_free[i] = true; } } } #endif for (i = 0; i < numberOfAttributes; i++) { if (isnull[i]) { hasnull = true; break; } } if (hasnull) infomask |= INDEX_NULL_MASK; hoff = IndexInfoFindDataOffset(infomask); #ifdef TOAST_INDEX_HACK data_size = heap_compute_data_size(tupleDescriptor, untoasted_values, isnull); #else data_size = heap_compute_data_size(tupleDescriptor, values, isnull); #endif size = hoff + data_size; size = MAXALIGN(size); /* be conservative */ tp = (char *) palloc0(size); tuple = (IndexTuple) tp; heap_fill_tuple(tupleDescriptor, #ifdef TOAST_INDEX_HACK untoasted_values, #else values, #endif isnull, (char *) tp + hoff, data_size, &tupmask, (hasnull ? (bits8 *) tp + sizeof(IndexTupleData) : NULL)); #ifdef TOAST_INDEX_HACK for (i = 0; i < numberOfAttributes; i++) { if (untoasted_free[i]) pfree(DatumGetPointer(untoasted_values[i])); } #endif /* * We do this because heap_fill_tuple wants to initialize a "tupmask" * which is used for HeapTuples, but we want an indextuple infomask. The * only relevant info is the "has variable attributes" field. We have * already set the hasnull bit above. */ if (tupmask & HEAP_HASVARWIDTH) infomask |= INDEX_VAR_MASK; /* Also assert we got rid of external attributes */ #ifdef TOAST_INDEX_HACK Assert((tupmask & HEAP_HASEXTERNAL) == 0); #endif /* * Here we make sure that the size will fit in the field reserved for it * in t_info. */ if ((size & INDEX_SIZE_MASK) != size) ereport(ERROR, (errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED), errmsg("index row requires %zu bytes, maximum size is %zu", size, (Size) INDEX_SIZE_MASK))); infomask |= size; /* * initialize metadata */ tuple->t_info = infomask; return tuple; }
Datum geom_from_geojson(PG_FUNCTION_ARGS) { #ifndef HAVE_LIBJSON elog(ERROR, "You need JSON-C for ST_GeomFromGeoJSON"); PG_RETURN_NULL(); #else /* HAVE_LIBJSON */ GSERIALIZED *geom; LWGEOM *lwgeom; text *geojson_input; int geojson_size; char *geojson; int root_srid=0; bool hasz=true; json_tokener* jstok = NULL; json_object* poObj = NULL; json_object* poObjSrs = NULL; /* Get the geojson stream */ if (PG_ARGISNULL(0)) PG_RETURN_NULL(); geojson_input = PG_GETARG_TEXT_P(0); geojson = text2cstring(geojson_input); geojson_size = VARSIZE(geojson_input) - VARHDRSZ; /* Begin to Parse json */ jstok = json_tokener_new(); poObj = json_tokener_parse_ex(jstok, geojson, -1); if( jstok->err != json_tokener_success) { char err[256]; snprintf(err, 256, "%s (at offset %d)", json_tokener_errors[jstok->err], jstok->char_offset); json_tokener_free(jstok); geojson_lwerror(err, 1); } json_tokener_free(jstok); poObjSrs = findMemberByName( poObj, "crs" ); if (poObjSrs != NULL) { json_object* poObjSrsType = findMemberByName( poObjSrs, "type" ); if (poObjSrsType != NULL) { json_object* poObjSrsProps = findMemberByName( poObjSrs, "properties" ); json_object* poNameURL = findMemberByName( poObjSrsProps, "name" ); const char* pszName = json_object_get_string( poNameURL ); root_srid = getSRIDbySRS(pszName); POSTGIS_DEBUGF(3, "getSRIDbySRS returned root_srid = %d.", root_srid ); } } lwgeom = parse_geojson(poObj, &hasz, &root_srid); lwgeom_add_bbox(lwgeom); if (root_srid && lwgeom->srid == -1) lwgeom->srid = root_srid; if (!hasz) { LWGEOM *tmp = lwgeom_force_2d(lwgeom); lwgeom_free(lwgeom); lwgeom = tmp; POSTGIS_DEBUG(2, "geom_from_geojson called."); } geom = geometry_serialize(lwgeom); lwgeom_free(lwgeom); PG_RETURN_POINTER(geom); #endif }
/* ---------- * toast_fetch_datum - * * Reconstruct an in memory Datum from the chunks saved * in the toast relation * ---------- */ static struct varlena * toast_fetch_datum(struct varlena * attr) { Relation toastrel; Relation toastidx; ScanKeyData toastkey; SysScanDesc toastscan; HeapTuple ttup; TupleDesc toasttupDesc; struct varlena *result; struct varatt_external toast_pointer; int32 ressize; int32 residx, nextidx; int32 numchunks; Pointer chunk; bool isnull; char *chunkdata; int32 chunksize; /* Must copy to access aligned fields */ VARATT_EXTERNAL_GET_POINTER(toast_pointer, attr); ressize = toast_pointer.va_extsize; numchunks = ((ressize - 1) / TOAST_MAX_CHUNK_SIZE) + 1; result = (struct varlena *) palloc(ressize + VARHDRSZ); if (VARATT_EXTERNAL_IS_COMPRESSED(toast_pointer)) SET_VARSIZE_COMPRESSED(result, ressize + VARHDRSZ); else SET_VARSIZE(result, ressize + VARHDRSZ); /* * Open the toast relation and its index */ toastrel = heap_open(toast_pointer.va_toastrelid, AccessShareLock); toasttupDesc = toastrel->rd_att; toastidx = index_open(toastrel->rd_rel->reltoastidxid, AccessShareLock); /* * Setup a scan key to fetch from the index by va_valueid */ ScanKeyInit(&toastkey, (AttrNumber) 1, BTEqualStrategyNumber, F_OIDEQ, ObjectIdGetDatum(toast_pointer.va_valueid)); /* * Read the chunks by index * * Note that because the index is actually on (valueid, chunkidx) we will * see the chunks in chunkidx order, even though we didn't explicitly ask * for it. */ nextidx = 0; toastscan = systable_beginscan_ordered(toastrel, toastidx, SnapshotToast, 1, &toastkey); while ((ttup = systable_getnext_ordered(toastscan, ForwardScanDirection)) != NULL) { /* * Have a chunk, extract the sequence number and the data */ residx = DatumGetInt32(fastgetattr(ttup, 2, toasttupDesc, &isnull)); Assert(!isnull); chunk = DatumGetPointer(fastgetattr(ttup, 3, toasttupDesc, &isnull)); Assert(!isnull); if (!VARATT_IS_EXTENDED(chunk)) { chunksize = VARSIZE(chunk) - VARHDRSZ; chunkdata = VARDATA(chunk); } else if (VARATT_IS_SHORT(chunk)) { /* could happen due to heap_form_tuple doing its thing */ chunksize = VARSIZE_SHORT(chunk) - VARHDRSZ_SHORT; chunkdata = VARDATA_SHORT(chunk); } else { /* should never happen */ elog(ERROR, "found toasted toast chunk for toast value %u in %s", toast_pointer.va_valueid, RelationGetRelationName(toastrel)); chunksize = 0; /* keep compiler quiet */ chunkdata = NULL; } /* * Some checks on the data we've found */ if (residx != nextidx) elog(ERROR, "unexpected chunk number %d (expected %d) for toast value %u in %s", residx, nextidx, toast_pointer.va_valueid, RelationGetRelationName(toastrel)); if (residx < numchunks - 1) { if (chunksize != TOAST_MAX_CHUNK_SIZE) elog(ERROR, "unexpected chunk size %d (expected %d) in chunk %d of %d for toast value %u in %s", chunksize, (int) TOAST_MAX_CHUNK_SIZE, residx, numchunks, toast_pointer.va_valueid, RelationGetRelationName(toastrel)); } else if (residx == numchunks - 1) { if ((residx * TOAST_MAX_CHUNK_SIZE + chunksize) != ressize) elog(ERROR, "unexpected chunk size %d (expected %d) in final chunk %d for toast value %u in %s", chunksize, (int) (ressize - residx * TOAST_MAX_CHUNK_SIZE), residx, toast_pointer.va_valueid, RelationGetRelationName(toastrel)); } else elog(ERROR, "unexpected chunk number %d (out of range %d..%d) for toast value %u in %s", residx, 0, numchunks - 1, toast_pointer.va_valueid, RelationGetRelationName(toastrel)); /* * Copy the data into proper place in our result */ memcpy(VARDATA(result) + residx * TOAST_MAX_CHUNK_SIZE, chunkdata, chunksize); nextidx++; } /* * Final checks that we successfully fetched the datum */ if (nextidx != numchunks) elog(ERROR, "missing chunk number %d for toast value %u in %s", nextidx, toast_pointer.va_valueid, RelationGetRelationName(toastrel)); /* * End scan and close relations */ systable_endscan_ordered(toastscan); index_close(toastidx, AccessShareLock); heap_close(toastrel, AccessShareLock); return result; }
/* ---------- * heap_tuple_untoast_attr_slice - * * Public entry point to get back part of a toasted value * from compression or external storage. * ---------- */ struct varlena * heap_tuple_untoast_attr_slice(struct varlena * attr, int32 sliceoffset, int32 slicelength) { struct varlena *preslice; struct varlena *result; char *attrdata; int32 attrsize; if (VARATT_IS_EXTERNAL(attr)) { struct varatt_external toast_pointer; VARATT_EXTERNAL_GET_POINTER(toast_pointer, attr); /* fast path for non-compressed external datums */ if (!VARATT_EXTERNAL_IS_COMPRESSED(toast_pointer)) return toast_fetch_datum_slice(attr, sliceoffset, slicelength); /* fetch it back (compressed marker will get set automatically) */ preslice = toast_fetch_datum(attr); } else preslice = attr; if (VARATT_IS_COMPRESSED(preslice)) { PGLZ_Header *tmp = (PGLZ_Header *) preslice; Size size = PGLZ_RAW_SIZE(tmp) + VARHDRSZ; preslice = (struct varlena *) palloc(size); SET_VARSIZE(preslice, size); pglz_decompress(tmp, VARDATA(preslice)); if (tmp != (PGLZ_Header *) attr) pfree(tmp); } if (VARATT_IS_SHORT(preslice)) { attrdata = VARDATA_SHORT(preslice); attrsize = VARSIZE_SHORT(preslice) - VARHDRSZ_SHORT; } else { attrdata = VARDATA(preslice); attrsize = VARSIZE(preslice) - VARHDRSZ; } /* slicing of datum for compressed cases and plain value */ if (sliceoffset >= attrsize) { sliceoffset = 0; slicelength = 0; } if (((sliceoffset + slicelength) > attrsize) || slicelength < 0) slicelength = attrsize - sliceoffset; result = (struct varlena *) palloc(slicelength + VARHDRSZ); SET_VARSIZE(result, slicelength + VARHDRSZ); memcpy(VARDATA(result), attrdata + sliceoffset, slicelength); if (preslice != attr) pfree(preslice); return result; }
Datum tuple_data_split(PG_FUNCTION_ARGS) { Oid relid; bytea *raw_data; uint16 t_infomask; uint16 t_infomask2; char *t_bits_str; bool do_detoast = false; bits8 *t_bits = NULL; Datum res; relid = PG_GETARG_OID(0); raw_data = PG_ARGISNULL(1) ? NULL : PG_GETARG_BYTEA_P(1); t_infomask = PG_GETARG_INT16(2); t_infomask2 = PG_GETARG_INT16(3); t_bits_str = PG_ARGISNULL(4) ? NULL : text_to_cstring(PG_GETARG_TEXT_PP(4)); if (PG_NARGS() >= 6) do_detoast = PG_GETARG_BOOL(5); if (!superuser()) ereport(ERROR, (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), errmsg("must be superuser to use raw page functions"))); if (!raw_data) PG_RETURN_NULL(); /* * Convert t_bits string back to the bits8 array as represented in the * tuple header. */ if (t_infomask & HEAP_HASNULL) { int bits_str_len; int bits_len; bits_len = BITMAPLEN(t_infomask2 & HEAP_NATTS_MASK) * BITS_PER_BYTE; if (!t_bits_str) ereport(ERROR, (errcode(ERRCODE_DATA_CORRUPTED), errmsg("argument of t_bits is null, but it is expected to be null and %d character long", bits_len))); bits_str_len = strlen(t_bits_str); if (bits_len != bits_str_len) ereport(ERROR, (errcode(ERRCODE_DATA_CORRUPTED), errmsg("unexpected length of t_bits %u, expected %d", bits_str_len, bits_len))); /* do the conversion */ t_bits = text_to_bits(t_bits_str, bits_str_len); } else { if (t_bits_str) ereport(ERROR, (errcode(ERRCODE_DATA_CORRUPTED), errmsg("t_bits string is expected to be NULL, but instead it is %zu bytes length", strlen(t_bits_str)))); } /* Split tuple data */ res = tuple_data_split_internal(relid, (char *) raw_data + VARHDRSZ, VARSIZE(raw_data) - VARHDRSZ, t_infomask, t_infomask2, t_bits, do_detoast); if (t_bits) pfree(t_bits); PG_RETURN_ARRAYTYPE_P(res); }
/* * hstoreUpgrade: PG_DETOAST_DATUM plus support for conversion of old hstores */ HStore * hstoreUpgrade(Datum orig) { HStore *hs = (HStore *) PG_DETOAST_DATUM(orig); int valid_new; int valid_old; bool writable; /* Return immediately if no conversion needed */ if ((hs->size_ & HS_FLAG_NEWVERSION) || hs->size_ == 0 || (VARSIZE(hs) < 32768 && HSE_ISFIRST((ARRPTR(hs)[0])))) return hs; valid_new = hstoreValidNewFormat(hs); valid_old = hstoreValidOldFormat(hs); /* Do we have a writable copy? */ writable = ((void *) hs != (void *) DatumGetPointer(orig)); if (!valid_old || hs->size_ == 0) { if (valid_new) { /* * force the "new version" flag and the correct varlena length, * but only if we have a writable copy already (which we almost * always will, since short new-format values won't come through * here) */ if (writable) { HS_SETCOUNT(hs, HS_COUNT(hs)); HS_FIXSIZE(hs, HS_COUNT(hs)); } return hs; } else { elog(ERROR, "invalid hstore value found"); } } /* * this is the tricky edge case. It is only possible in some quite extreme * cases (the hstore must have had a lot of wasted padding space at the * end). But the only way a "new" hstore value could get here is if we're * upgrading in place from a pre-release version of hstore-new (NOT * contrib/hstore), so we work off the following assumptions: 1. If you're * moving from old contrib/hstore to hstore-new, you're required to fix up * any potential conflicts first, e.g. by running ALTER TABLE ... USING * col::text::hstore; on all hstore columns before upgrading. 2. If you're * moving from old contrib/hstore to new contrib/hstore, then "new" values * are impossible here 3. If you're moving from pre-release hstore-new to * hstore-new, then "old" values are impossible here 4. If you're moving * from pre-release hstore-new to new contrib/hstore, you're not doing so * as an in-place upgrade, so there is no issue So the upshot of all this * is that we can treat all the edge cases as "new" if we're being built * as hstore-new, and "old" if we're being built as contrib/hstore. * * XXX the WARNING can probably be downgraded to DEBUG1 once this has been * beta-tested. But for now, it would be very useful to know if anyone can * actually reach this case in a non-contrived setting. */ if (valid_new) { #if HSTORE_IS_HSTORE_NEW elog(WARNING, "ambiguous hstore value resolved as hstore-new"); /* * force the "new version" flag and the correct varlena length, but * only if we have a writable copy already (which we almost always * will, since short new-format values won't come through here) */ if (writable) { HS_SETCOUNT(hs, HS_COUNT(hs)); HS_FIXSIZE(hs, HS_COUNT(hs)); } return hs; #else elog(WARNING, "ambiguous hstore value resolved as hstore-old"); #endif } /* * must have an old-style value. Overwrite it in place as a new-style one, * making sure we have a writable copy first. */ if (!writable) hs = (HStore *) PG_DETOAST_DATUM_COPY(orig); { int count = hs->size_; HEntry *new_entries = ARRPTR(hs); HOldEntry *old_entries = (HOldEntry *) ARRPTR(hs); int i; for (i = 0; i < count; ++i) { uint32 pos = old_entries[i].pos; uint32 keylen = old_entries[i].keylen; uint32 vallen = old_entries[i].vallen; bool isnull = old_entries[i].valisnull; if (isnull) vallen = 0; new_entries[2 * i].entry = (pos + keylen) & HENTRY_POSMASK; new_entries[2 * i + 1].entry = (((pos + keylen + vallen) & HENTRY_POSMASK) | ((isnull) ? HENTRY_ISNULL : 0)); } if (count) new_entries[0].entry |= HENTRY_ISFIRST; HS_SETCOUNT(hs, count); HS_FIXSIZE(hs, count); } return hs; }
Datum hstore_delete_hstore(PG_FUNCTION_ARGS) { HStore *hs = PG_GETARG_HS(0); HStore *hs2 = PG_GETARG_HS(1); HStore *out = palloc(VARSIZE(hs)); int hs_count = HS_COUNT(hs); int hs2_count = HS_COUNT(hs2); char *ps, *ps2, *bufd, *pd; HEntry *es, *es2, *ed; int i, j; int outcount = 0; SET_VARSIZE(out, VARSIZE(hs)); HS_SETCOUNT(out, hs_count); /* temporary! */ ps = STRPTR(hs); es = ARRPTR(hs); ps2 = STRPTR(hs2); es2 = ARRPTR(hs2); bufd = pd = STRPTR(out); ed = ARRPTR(out); if (hs2_count == 0) { /* return a copy of the input, unchanged */ memcpy(out, hs, VARSIZE(hs)); HS_FIXSIZE(out, hs_count); HS_SETCOUNT(out, hs_count); PG_RETURN_POINTER(out); } /* * this is in effect a merge between hs and hs2, both of which are already * sorted by (keylen,key); we take keys from hs only; for equal keys, we * take the value from hs unless the values are equal */ for (i = j = 0; i < hs_count;) { int difference; if (j >= hs2_count) difference = -1; else { int skeylen = HS_KEYLEN(es, i); int s2keylen = HS_KEYLEN(es2, j); if (skeylen == s2keylen) difference = memcmp(HS_KEY(es, ps, i), HS_KEY(es2, ps2, j), skeylen); else difference = (skeylen > s2keylen) ? 1 : -1; } if (difference > 0) ++j; else if (difference == 0) { int svallen = HS_VALLEN(es, i); int snullval = HS_VALISNULL(es, i); if (snullval != HS_VALISNULL(es2, j) || (!snullval && (svallen != HS_VALLEN(es2, j) || memcmp(HS_VAL(es, ps, i), HS_VAL(es2, ps2, j), svallen) != 0))) { HS_COPYITEM(ed, bufd, pd, HS_KEY(es, ps, i), HS_KEYLEN(es, i), svallen, snullval); ++outcount; } ++i, ++j; } else { HS_COPYITEM(ed, bufd, pd, HS_KEY(es, ps, i), HS_KEYLEN(es, i), HS_VALLEN(es, i), HS_VALISNULL(es, i)); ++outcount; ++i; } } HS_FINALIZE(out, outcount, bufd, pd); PG_RETURN_POINTER(out); }
Datum hstore_concat(PG_FUNCTION_ARGS) { HStore *s1 = PG_GETARG_HS(0); HStore *s2 = PG_GETARG_HS(1); HStore *out = palloc(VARSIZE(s1) + VARSIZE(s2)); char *ps1, *ps2, *bufd, *pd; HEntry *es1, *es2, *ed; int s1idx; int s2idx; int s1count = HS_COUNT(s1); int s2count = HS_COUNT(s2); int outcount = 0; SET_VARSIZE(out, VARSIZE(s1) + VARSIZE(s2) - HSHRDSIZE); HS_SETCOUNT(out, s1count + s2count); if (s1count == 0) { /* return a copy of the input, unchanged */ memcpy(out, s2, VARSIZE(s2)); HS_FIXSIZE(out, s2count); HS_SETCOUNT(out, s2count); PG_RETURN_POINTER(out); } if (s2count == 0) { /* return a copy of the input, unchanged */ memcpy(out, s1, VARSIZE(s1)); HS_FIXSIZE(out, s1count); HS_SETCOUNT(out, s1count); PG_RETURN_POINTER(out); } ps1 = STRPTR(s1); ps2 = STRPTR(s2); bufd = pd = STRPTR(out); es1 = ARRPTR(s1); es2 = ARRPTR(s2); ed = ARRPTR(out); /* * this is in effect a merge between s1 and s2, both of which are already * sorted by (keylen,key); we take s2 for equal keys */ for (s1idx = s2idx = 0; s1idx < s1count || s2idx < s2count; ++outcount) { int difference; if (s1idx >= s1count) difference = 1; else if (s2idx >= s2count) difference = -1; else { int s1keylen = HS_KEYLEN(es1, s1idx); int s2keylen = HS_KEYLEN(es2, s2idx); if (s1keylen == s2keylen) difference = memcmp(HS_KEY(es1, ps1, s1idx), HS_KEY(es2, ps2, s2idx), s1keylen); else difference = (s1keylen > s2keylen) ? 1 : -1; } if (difference >= 0) { HS_COPYITEM(ed, bufd, pd, HS_KEY(es2, ps2, s2idx), HS_KEYLEN(es2, s2idx), HS_VALLEN(es2, s2idx), HS_VALISNULL(es2, s2idx)); ++s2idx; if (difference == 0) ++s1idx; } else { HS_COPYITEM(ed, bufd, pd, HS_KEY(es1, ps1, s1idx), HS_KEYLEN(es1, s1idx), HS_VALLEN(es1, s1idx), HS_VALISNULL(es1, s1idx)); ++s1idx; } } HS_FINALIZE(out, outcount, bufd, pd); PG_RETURN_POINTER(out); }
Datum hstore_delete_array(PG_FUNCTION_ARGS) { HStore *hs = PG_GETARG_HS(0); HStore *out = palloc(VARSIZE(hs)); int hs_count = HS_COUNT(hs); char *ps, *bufd, *pd; HEntry *es, *ed; int i, j; int outcount = 0; ArrayType *key_array = PG_GETARG_ARRAYTYPE_P(1); int nkeys; Pairs *key_pairs = hstoreArrayToPairs(key_array, &nkeys); SET_VARSIZE(out, VARSIZE(hs)); HS_SETCOUNT(out, hs_count); /* temporary! */ ps = STRPTR(hs); es = ARRPTR(hs); bufd = pd = STRPTR(out); ed = ARRPTR(out); if (nkeys == 0) { /* return a copy of the input, unchanged */ memcpy(out, hs, VARSIZE(hs)); HS_FIXSIZE(out, hs_count); HS_SETCOUNT(out, hs_count); PG_RETURN_POINTER(out); } /* * this is in effect a merge between hs and key_pairs, both of which are * already sorted by (keylen,key); we take keys from hs only */ for (i = j = 0; i < hs_count;) { int difference; if (j >= nkeys) difference = -1; else { int skeylen = HS_KEYLEN(es, i); if (skeylen == key_pairs[j].keylen) difference = memcmp(HS_KEY(es, ps, i), key_pairs[j].key, key_pairs[j].keylen); else difference = (skeylen > key_pairs[j].keylen) ? 1 : -1; } if (difference > 0) ++j; else if (difference == 0) ++i, ++j; else { HS_COPYITEM(ed, bufd, pd, HS_KEY(es, ps, i), HS_KEYLEN(es, i), HS_VALLEN(es, i), HS_VALISNULL(es, i)); ++outcount; ++i; } } HS_FINALIZE(out, outcount, bufd, pd); PG_RETURN_POINTER(out); }
/* ---------------- * printtup_internal_20 --- print a binary tuple in protocol 2.0 * * We use a different message type, i.e. 'B' instead of 'D' to * indicate a tuple in internal (binary) form. * * This is largely same as printtup_20, except we use binary formatting. * ---------------- */ static void printtup_internal_20(TupleTableSlot *slot, DestReceiver *self) { TupleDesc typeinfo = slot->tts_tupleDescriptor; DR_printtup *myState = (DR_printtup *) self; StringInfoData buf; int natts = typeinfo->natts; int i, j, k; /* Set or update my derived attribute info, if needed */ if (myState->attrinfo != typeinfo || myState->nattrs != natts) printtup_prepare_info(myState, typeinfo, natts); /* Make sure the tuple is fully deconstructed */ slot_getallattrs(slot); /* * tell the frontend to expect new tuple data (in binary style) */ pq_beginmessage(&buf, 'B'); /* * send a bitmap of which attributes are not null */ j = 0; k = 1 << 7; for (i = 0; i < natts; ++i) { if (!slot->tts_isnull[i]) j |= k; /* set bit if not null */ k >>= 1; if (k == 0) /* end of byte? */ { pq_sendint(&buf, j, 1); j = 0; k = 1 << 7; } } if (k != (1 << 7)) /* flush last partial byte */ pq_sendint(&buf, j, 1); /* * send the attributes of this tuple */ for (i = 0; i < natts; ++i) { PrinttupAttrInfo *thisState = myState->myinfo + i; Datum origattr = slot->tts_values[i], attr; bytea *outputbytes; if (slot->tts_isnull[i]) continue; Assert(thisState->format == 1); /* * If we have a toasted datum, forcibly detoast it here to avoid * memory leakage inside the type's output routine. */ if (thisState->typisvarlena) attr = PointerGetDatum(PG_DETOAST_DATUM(origattr)); else attr = origattr; outputbytes = SendFunctionCall(&thisState->finfo, attr); /* We assume the result will not have been toasted */ pq_sendint(&buf, VARSIZE(outputbytes) - VARHDRSZ, 4); pq_sendbytes(&buf, VARDATA(outputbytes), VARSIZE(outputbytes) - VARHDRSZ); pfree(outputbytes); /* Clean up detoasted copy, if any */ if (DatumGetPointer(attr) != DatumGetPointer(origattr)) pfree(DatumGetPointer(attr)); } pq_endmessage(&buf); }
/* ---------------- * printtup --- print a tuple in protocol 3.0 * ---------------- */ static void printtup(TupleTableSlot *slot, DestReceiver *self) { TupleDesc typeinfo = slot->tts_tupleDescriptor; DR_printtup *myState = (DR_printtup *) self; StringInfoData buf; int natts = typeinfo->natts; int i; /* Set or update my derived attribute info, if needed */ if (myState->attrinfo != typeinfo || myState->nattrs != natts) printtup_prepare_info(myState, typeinfo, natts); /* Make sure the tuple is fully deconstructed */ slot_getallattrs(slot); /* * Prepare a DataRow message */ pq_beginmessage(&buf, 'D'); pq_sendint(&buf, natts, 2); /* * send the attributes of this tuple */ for (i = 0; i < natts; ++i) { PrinttupAttrInfo *thisState = myState->myinfo + i; Datum origattr = slot->tts_values[i], attr; if (slot->tts_isnull[i]) { pq_sendint(&buf, -1, 4); continue; } /* * If we have a toasted datum, forcibly detoast it here to avoid * memory leakage inside the type's output routine. */ if (thisState->typisvarlena) attr = PointerGetDatum(PG_DETOAST_DATUM(origattr)); else attr = origattr; if (thisState->format == 0) { /* Text output */ char *outputstr; outputstr = OutputFunctionCall(&thisState->finfo, attr); pq_sendcountedtext(&buf, outputstr, strlen(outputstr), false); pfree(outputstr); } else { /* Binary output */ bytea *outputbytes; outputbytes = SendFunctionCall(&thisState->finfo, attr); pq_sendint(&buf, VARSIZE(outputbytes) - VARHDRSZ, 4); pq_sendbytes(&buf, VARDATA(outputbytes), VARSIZE(outputbytes) - VARHDRSZ); pfree(outputbytes); } /* Clean up detoasted copy, if any */ if (DatumGetPointer(attr) != DatumGetPointer(origattr)) pfree(DatumGetPointer(attr)); } pq_endmessage(&buf); }
Datum xmlnode_to_xmldoc(PG_FUNCTION_ARGS) { XMLCompNodeHdr rootNode, rootDoc; unsigned int sizeNew, dataSizeNew; xmlnode node = (xmlnode) PG_GETARG_VARLENA_P(0); xmldoc document = NULL; char *docData; unsigned int sizeOrig = VARSIZE(node); unsigned int dataSizeOrig = sizeOrig - VARHDRSZ; char *nodeData = (char *) VARDATA(node); /* * The new root will start where last value of the array (i.e. offset of * the current root) was so far */ XMLNodeOffset rootOffsetNew = dataSizeOrig - sizeof(XMLNodeOffset); /* Find that 'old last (root offset) value' ... */ XMLNodeOffset *rootOffPtrOrig = (XMLNodeOffset *) (nodeData + rootOffsetNew); /* ... and read it */ XMLNodeOffset rootOffsetOrig = *rootOffPtrOrig; /* * Compute 'relative reference' of the 'old root' that the document ('new * root') will remember */ XMLNodeOffset dist = rootOffsetNew - rootOffsetOrig; XMLNodeOffset *rootOffPtrNew; char bwidth = getXMLNodeOffsetByteWidth(dist); rootNode = (XMLCompNodeHdr) (nodeData + rootOffsetOrig); if (rootNode->common.kind == XMLNODE_ELEMENT) { /* * If document should contain only one node, it must be element. See * http://www.w3.org/TR/2008/REC-xml-20081126/#NT-document */ char *refTargPtr; sizeNew = sizeOrig + sizeof(XMLCompNodeHdrData) + bwidth; dataSizeNew = sizeNew - VARHDRSZ; document = (xmldoc) palloc(sizeNew); docData = (char *) VARDATA(document); memcpy(docData, nodeData, rootOffsetNew); rootDoc = (XMLCompNodeHdr) (docData + rootOffsetNew); rootDoc->common.kind = XMLNODE_DOC; rootDoc->common.flags = 0; XNODE_SET_REF_BWIDTH(rootDoc, bwidth); rootDoc->children = 1; refTargPtr = (char *) rootDoc + sizeof(XMLCompNodeHdrData); writeXMLNodeOffset(dist, &refTargPtr, bwidth, false); rootOffPtrNew = (XMLNodeOffset *) (docData + dataSizeNew - sizeof(XMLNodeOffset)); *rootOffPtrNew = rootOffsetNew; SET_VARSIZE(document, sizeNew); } else if (rootNode->common.kind == XMLNODE_DOC_FRAGMENT) { checkXMLWellFormedness(rootNode); document = (xmldoc) palloc(sizeOrig); docData = (char *) VARDATA(document); memcpy(document, node, sizeOrig); rootDoc = (XMLCompNodeHdr) (docData + rootOffsetOrig); rootDoc->common.kind = XMLNODE_DOC; SET_VARSIZE(document, sizeOrig); } else { elog(ERROR, "%s can't be cast to XML document", getXMLNodeKindStr(rootNode->common.kind)); } PG_RETURN_POINTER(document); }
static void clear_and_pfree(text *p) { memset(p, 0, VARSIZE(p)); pfree(p); }
/* ---------- * toast_save_datum - * * Save one single datum into the secondary relation and return * a Datum reference for it. * ---------- */ static Datum toast_save_datum(Relation rel, Datum value, int options) { Relation toastrel; Relation toastidx; HeapTuple toasttup; TupleDesc toasttupDesc; Datum t_values[3]; bool t_isnull[3]; CommandId mycid = GetCurrentCommandId(true); struct varlena *result; struct varatt_external toast_pointer; struct { struct varlena hdr; char data[TOAST_MAX_CHUNK_SIZE]; /* make struct big enough */ int32 align_it; /* ensure struct is aligned well enough */ } chunk_data; int32 chunk_size; int32 chunk_seq = 0; char *data_p; int32 data_todo; Pointer dval = DatumGetPointer(value); /* * Open the toast relation and its index. We can use the index to check * uniqueness of the OID we assign to the toasted item, even though it has * additional columns besides OID. */ toastrel = heap_open(rel->rd_rel->reltoastrelid, RowExclusiveLock); toasttupDesc = toastrel->rd_att; toastidx = index_open(toastrel->rd_rel->reltoastidxid, RowExclusiveLock); /* * Get the data pointer and length, and compute va_rawsize and va_extsize. * * va_rawsize is the size of the equivalent fully uncompressed datum, so * we have to adjust for short headers. * * va_extsize is the actual size of the data payload in the toast records. */ if (VARATT_IS_SHORT(dval)) { data_p = VARDATA_SHORT(dval); data_todo = VARSIZE_SHORT(dval) - VARHDRSZ_SHORT; toast_pointer.va_rawsize = data_todo + VARHDRSZ; /* as if not short */ toast_pointer.va_extsize = data_todo; } else if (VARATT_IS_COMPRESSED(dval)) { data_p = VARDATA(dval); data_todo = VARSIZE(dval) - VARHDRSZ; /* rawsize in a compressed datum is just the size of the payload */ toast_pointer.va_rawsize = VARRAWSIZE_4B_C(dval) + VARHDRSZ; toast_pointer.va_extsize = data_todo; /* Assert that the numbers look like it's compressed */ Assert(VARATT_EXTERNAL_IS_COMPRESSED(toast_pointer)); } else { data_p = VARDATA(dval); data_todo = VARSIZE(dval) - VARHDRSZ; toast_pointer.va_rawsize = VARSIZE(dval); toast_pointer.va_extsize = data_todo; } toast_pointer.va_valueid = GetNewOidWithIndex(toastrel, RelationGetRelid(toastidx), (AttrNumber) 1); toast_pointer.va_toastrelid = rel->rd_rel->reltoastrelid; /* * Initialize constant parts of the tuple data */ t_values[0] = ObjectIdGetDatum(toast_pointer.va_valueid); t_values[2] = PointerGetDatum(&chunk_data); t_isnull[0] = false; t_isnull[1] = false; t_isnull[2] = false; /* * Split up the item into chunks */ while (data_todo > 0) { /* * Calculate the size of this chunk */ chunk_size = Min(TOAST_MAX_CHUNK_SIZE, data_todo); /* * Build a tuple and store it */ t_values[1] = Int32GetDatum(chunk_seq++); SET_VARSIZE(&chunk_data, chunk_size + VARHDRSZ); memcpy(VARDATA(&chunk_data), data_p, chunk_size); toasttup = heap_form_tuple(toasttupDesc, t_values, t_isnull); heap_insert(toastrel, toasttup, mycid, options, NULL); /* * Create the index entry. We cheat a little here by not using * FormIndexDatum: this relies on the knowledge that the index columns * are the same as the initial columns of the table. * * Note also that there had better not be any user-created index on * the TOAST table, since we don't bother to update anything else. */ index_insert(toastidx, t_values, t_isnull, &(toasttup->t_self), toastrel, toastidx->rd_index->indisunique); /* * Free memory */ heap_freetuple(toasttup); /* * Move on to next chunk */ data_todo -= chunk_size; data_p += chunk_size; } /* * Done - close toast relation */ index_close(toastidx, RowExclusiveLock); heap_close(toastrel, RowExclusiveLock); /* * Create the TOAST pointer value that we'll return */ result = (struct varlena *) palloc(TOAST_POINTER_SIZE); SET_VARSIZE_EXTERNAL(result, TOAST_POINTER_SIZE); memcpy(VARDATA_EXTERNAL(result), &toast_pointer, sizeof(toast_pointer)); return PointerGetDatum(result); }
static MBuf * create_mbuf_from_vardata(text *data) { return mbuf_create_from_data((uint8 *) VARDATA(data), VARSIZE(data) - VARHDRSZ); }
/* ---------- * toast_fetch_datum_slice - * * Reconstruct a segment of a Datum from the chunks saved * in the toast relation * ---------- */ static struct varlena * toast_fetch_datum_slice(struct varlena * attr, int32 sliceoffset, int32 length) { Relation toastrel; Relation toastidx; ScanKeyData toastkey[3]; int nscankeys; SysScanDesc toastscan; HeapTuple ttup; TupleDesc toasttupDesc; struct varlena *result; struct varatt_external toast_pointer; int32 attrsize; int32 residx; int32 nextidx; int numchunks; int startchunk; int endchunk; int32 startoffset; int32 endoffset; int totalchunks; Pointer chunk; bool isnull; char *chunkdata; int32 chunksize; int32 chcpystrt; int32 chcpyend; Assert(VARATT_IS_EXTERNAL(attr)); /* Must copy to access aligned fields */ VARATT_EXTERNAL_GET_POINTER(toast_pointer, attr); /* * It's nonsense to fetch slices of a compressed datum -- this isn't lo_* * we can't return a compressed datum which is meaningful to toast later */ Assert(!VARATT_EXTERNAL_IS_COMPRESSED(toast_pointer)); attrsize = toast_pointer.va_extsize; totalchunks = ((attrsize - 1) / TOAST_MAX_CHUNK_SIZE) + 1; if (sliceoffset >= attrsize) { sliceoffset = 0; length = 0; } if (((sliceoffset + length) > attrsize) || length < 0) length = attrsize - sliceoffset; result = (struct varlena *) palloc(length + VARHDRSZ); if (VARATT_EXTERNAL_IS_COMPRESSED(toast_pointer)) SET_VARSIZE_COMPRESSED(result, length + VARHDRSZ); else SET_VARSIZE(result, length + VARHDRSZ); if (length == 0) return result; /* Can save a lot of work at this point! */ startchunk = sliceoffset / TOAST_MAX_CHUNK_SIZE; endchunk = (sliceoffset + length - 1) / TOAST_MAX_CHUNK_SIZE; numchunks = (endchunk - startchunk) + 1; startoffset = sliceoffset % TOAST_MAX_CHUNK_SIZE; endoffset = (sliceoffset + length - 1) % TOAST_MAX_CHUNK_SIZE; /* * Open the toast relation and its index */ toastrel = heap_open(toast_pointer.va_toastrelid, AccessShareLock); toasttupDesc = toastrel->rd_att; toastidx = index_open(toastrel->rd_rel->reltoastidxid, AccessShareLock); /* * Setup a scan key to fetch from the index. This is either two keys or * three depending on the number of chunks. */ ScanKeyInit(&toastkey[0], (AttrNumber) 1, BTEqualStrategyNumber, F_OIDEQ, ObjectIdGetDatum(toast_pointer.va_valueid)); /* * Use equality condition for one chunk, a range condition otherwise: */ if (numchunks == 1) { ScanKeyInit(&toastkey[1], (AttrNumber) 2, BTEqualStrategyNumber, F_INT4EQ, Int32GetDatum(startchunk)); nscankeys = 2; } else { ScanKeyInit(&toastkey[1], (AttrNumber) 2, BTGreaterEqualStrategyNumber, F_INT4GE, Int32GetDatum(startchunk)); ScanKeyInit(&toastkey[2], (AttrNumber) 2, BTLessEqualStrategyNumber, F_INT4LE, Int32GetDatum(endchunk)); nscankeys = 3; } /* * Read the chunks by index * * The index is on (valueid, chunkidx) so they will come in order */ nextidx = startchunk; toastscan = systable_beginscan_ordered(toastrel, toastidx, SnapshotToast, nscankeys, toastkey); while ((ttup = systable_getnext_ordered(toastscan, ForwardScanDirection)) != NULL) { /* * Have a chunk, extract the sequence number and the data */ residx = DatumGetInt32(fastgetattr(ttup, 2, toasttupDesc, &isnull)); Assert(!isnull); chunk = DatumGetPointer(fastgetattr(ttup, 3, toasttupDesc, &isnull)); Assert(!isnull); if (!VARATT_IS_EXTENDED(chunk)) { chunksize = VARSIZE(chunk) - VARHDRSZ; chunkdata = VARDATA(chunk); } else if (VARATT_IS_SHORT(chunk)) { /* could happen due to heap_form_tuple doing its thing */ chunksize = VARSIZE_SHORT(chunk) - VARHDRSZ_SHORT; chunkdata = VARDATA_SHORT(chunk); } else { /* should never happen */ elog(ERROR, "found toasted toast chunk for toast value %u in %s", toast_pointer.va_valueid, RelationGetRelationName(toastrel)); chunksize = 0; /* keep compiler quiet */ chunkdata = NULL; } /* * Some checks on the data we've found */ if ((residx != nextidx) || (residx > endchunk) || (residx < startchunk)) elog(ERROR, "unexpected chunk number %d (expected %d) for toast value %u in %s", residx, nextidx, toast_pointer.va_valueid, RelationGetRelationName(toastrel)); if (residx < totalchunks - 1) { if (chunksize != TOAST_MAX_CHUNK_SIZE) elog(ERROR, "unexpected chunk size %d (expected %d) in chunk %d of %d for toast value %u in %s when fetching slice", chunksize, (int) TOAST_MAX_CHUNK_SIZE, residx, totalchunks, toast_pointer.va_valueid, RelationGetRelationName(toastrel)); } else if (residx == totalchunks - 1) { if ((residx * TOAST_MAX_CHUNK_SIZE + chunksize) != attrsize) elog(ERROR, "unexpected chunk size %d (expected %d) in final chunk %d for toast value %u in %s when fetching slice", chunksize, (int) (attrsize - residx * TOAST_MAX_CHUNK_SIZE), residx, toast_pointer.va_valueid, RelationGetRelationName(toastrel)); } else elog(ERROR, "unexpected chunk number %d (out of range %d..%d) for toast value %u in %s", residx, 0, totalchunks - 1, toast_pointer.va_valueid, RelationGetRelationName(toastrel)); /* * Copy the data into proper place in our result */ chcpystrt = 0; chcpyend = chunksize - 1; if (residx == startchunk) chcpystrt = startoffset; if (residx == endchunk) chcpyend = endoffset; memcpy(VARDATA(result) + (residx * TOAST_MAX_CHUNK_SIZE - sliceoffset) + chcpystrt, chunkdata + chcpystrt, (chcpyend - chcpystrt) + 1); nextidx++; } /* * Final checks that we successfully fetched the datum */ if (nextidx != (endchunk + 1)) elog(ERROR, "missing chunk number %d for toast value %u in %s", nextidx, toast_pointer.va_valueid, RelationGetRelationName(toastrel)); /* * End scan and close relations */ systable_endscan_ordered(toastscan); index_close(toastidx, AccessShareLock); heap_close(toastrel, AccessShareLock); return result; }
static bytea * encrypt_internal(int is_pubenc, int is_text, text *data, text *key, text *args) { MBuf *src, *dst; uint8 tmp[VARHDRSZ]; uint8 *restmp; bytea *res; int res_len; PGP_Context *ctx; int err; struct debug_expect ex; text *tmp_data = NULL; /* * Add data and key info RNG. */ add_entropy(data, key, NULL); init_work(&ctx, is_text, args, &ex); if (is_text && pgp_get_unicode_mode(ctx)) { tmp_data = convert_to_utf8(data); if (tmp_data == data) tmp_data = NULL; else data = tmp_data; } src = create_mbuf_from_vardata(data); dst = mbuf_create(VARSIZE(data) + 128); /* * reserve room for header */ mbuf_append(dst, tmp, VARHDRSZ); /* * set key */ if (is_pubenc) { MBuf *kbuf = create_mbuf_from_vardata(key); err = pgp_set_pubkey(ctx, kbuf, NULL, 0, 0); mbuf_free(kbuf); } else err = pgp_set_symkey(ctx, (uint8 *) VARDATA(key), VARSIZE(key) - VARHDRSZ); /* * encrypt */ if (err >= 0) err = pgp_encrypt(ctx, src, dst); /* * check for error */ if (err) { if (ex.debug) px_set_debug_handler(NULL); if (tmp_data) clear_and_pfree(tmp_data); pgp_free(ctx); mbuf_free(src); mbuf_free(dst); ereport(ERROR, (errcode(ERRCODE_EXTERNAL_ROUTINE_INVOCATION_EXCEPTION), errmsg("%s", px_strerror(err)))); } /* res_len includes VARHDRSZ */ res_len = mbuf_steal_data(dst, &restmp); res = (bytea *) restmp; SET_VARSIZE(res, res_len); if (tmp_data) clear_and_pfree(tmp_data); pgp_free(ctx); mbuf_free(src); mbuf_free(dst); px_set_debug_handler(NULL); return res; }
/* ---------- * toast_insert_or_update - * * Delete no-longer-used toast-entries and create new ones to * make the new tuple fit on INSERT or UPDATE * * Inputs: * newtup: the candidate new tuple to be inserted * oldtup: the old row version for UPDATE, or NULL for INSERT * options: options to be passed to heap_insert() for toast rows * Result: * either newtup if no toasting is needed, or a palloc'd modified tuple * that is what should actually get stored * * NOTE: neither newtup nor oldtup will be modified. This is a change * from the pre-8.1 API of this routine. * ---------- */ HeapTuple toast_insert_or_update(Relation rel, HeapTuple newtup, HeapTuple oldtup, int options) { HeapTuple result_tuple; TupleDesc tupleDesc; Form_pg_attribute *att; int numAttrs; int i; bool need_change = false; bool need_free = false; bool need_delold = false; bool has_nulls = false; Size maxDataLen; Size hoff; char toast_action[MaxHeapAttributeNumber]; bool toast_isnull[MaxHeapAttributeNumber]; bool toast_oldisnull[MaxHeapAttributeNumber]; Datum toast_values[MaxHeapAttributeNumber]; Datum toast_oldvalues[MaxHeapAttributeNumber]; int32 toast_sizes[MaxHeapAttributeNumber]; bool toast_free[MaxHeapAttributeNumber]; bool toast_delold[MaxHeapAttributeNumber]; /* * We should only ever be called for tuples of plain relations --- * recursing on a toast rel is bad news. */ Assert(rel->rd_rel->relkind == RELKIND_RELATION); /* * Get the tuple descriptor and break down the tuple(s) into fields. */ tupleDesc = rel->rd_att; att = tupleDesc->attrs; numAttrs = tupleDesc->natts; Assert(numAttrs <= MaxHeapAttributeNumber); heap_deform_tuple(newtup, tupleDesc, toast_values, toast_isnull); if (oldtup != NULL) heap_deform_tuple(oldtup, tupleDesc, toast_oldvalues, toast_oldisnull); /* ---------- * Then collect information about the values given * * NOTE: toast_action[i] can have these values: * ' ' default handling * 'p' already processed --- don't touch it * 'x' incompressible, but OK to move off * * NOTE: toast_sizes[i] is only made valid for varlena attributes with * toast_action[i] different from 'p'. * ---------- */ memset(toast_action, ' ', numAttrs * sizeof(char)); memset(toast_free, 0, numAttrs * sizeof(bool)); memset(toast_delold, 0, numAttrs * sizeof(bool)); for (i = 0; i < numAttrs; i++) { struct varlena *old_value; struct varlena *new_value; if (oldtup != NULL) { /* * For UPDATE get the old and new values of this attribute */ old_value = (struct varlena *) DatumGetPointer(toast_oldvalues[i]); new_value = (struct varlena *) DatumGetPointer(toast_values[i]); /* * If the old value is an external stored one, check if it has * changed so we have to delete it later. */ if (att[i]->attlen == -1 && !toast_oldisnull[i] && VARATT_IS_EXTERNAL(old_value)) { if (toast_isnull[i] || !VARATT_IS_EXTERNAL(new_value) || memcmp((char *) old_value, (char *) new_value, VARSIZE_EXTERNAL(old_value)) != 0) { /* * The old external stored value isn't needed any more * after the update */ toast_delold[i] = true; need_delold = true; } else { /* * This attribute isn't changed by this update so we reuse * the original reference to the old value in the new * tuple. */ toast_action[i] = 'p'; continue; } } } else { /* * For INSERT simply get the new value */ new_value = (struct varlena *) DatumGetPointer(toast_values[i]); } /* * Handle NULL attributes */ if (toast_isnull[i]) { toast_action[i] = 'p'; has_nulls = true; continue; } /* * Now look at varlena attributes */ if (att[i]->attlen == -1) { /* * If the table's attribute says PLAIN always, force it so. */ if (att[i]->attstorage == 'p') toast_action[i] = 'p'; /* * We took care of UPDATE above, so any external value we find * still in the tuple must be someone else's we cannot reuse. * Fetch it back (without decompression, unless we are forcing * PLAIN storage). If necessary, we'll push it out as a new * external value below. */ if (VARATT_IS_EXTERNAL(new_value)) { if (att[i]->attstorage == 'p') new_value = heap_tuple_untoast_attr(new_value); else new_value = heap_tuple_fetch_attr(new_value); toast_values[i] = PointerGetDatum(new_value); toast_free[i] = true; need_change = true; need_free = true; } /* * Remember the size of this attribute */ toast_sizes[i] = VARSIZE_ANY(new_value); } else { /* * Not a varlena attribute, plain storage always */ toast_action[i] = 'p'; } } /* ---------- * Compress and/or save external until data fits into target length * * 1: Inline compress attributes with attstorage 'x', and store very * large attributes with attstorage 'x' or 'e' external immediately * 2: Store attributes with attstorage 'x' or 'e' external * 3: Inline compress attributes with attstorage 'm' * 4: Store attributes with attstorage 'm' external * ---------- */ /* compute header overhead --- this should match heap_form_tuple() */ hoff = offsetof(HeapTupleHeaderData, t_bits); if (has_nulls) hoff += BITMAPLEN(numAttrs); if (newtup->t_data->t_infomask & HEAP_HASOID) hoff += sizeof(Oid); hoff = MAXALIGN(hoff); Assert(hoff == newtup->t_data->t_hoff); /* now convert to a limit on the tuple data size */ maxDataLen = TOAST_TUPLE_TARGET - hoff; /* * Look for attributes with attstorage 'x' to compress. Also find large * attributes with attstorage 'x' or 'e', and store them external. */ while (heap_compute_data_size(tupleDesc, toast_values, toast_isnull) > maxDataLen) { int biggest_attno = -1; int32 biggest_size = MAXALIGN(TOAST_POINTER_SIZE); Datum old_value; Datum new_value; /* * Search for the biggest yet unprocessed internal attribute */ for (i = 0; i < numAttrs; i++) { if (toast_action[i] != ' ') continue; if (VARATT_IS_EXTERNAL(DatumGetPointer(toast_values[i]))) continue; /* can't happen, toast_action would be 'p' */ if (VARATT_IS_COMPRESSED(DatumGetPointer(toast_values[i]))) continue; if (att[i]->attstorage != 'x' && att[i]->attstorage != 'e') continue; if (toast_sizes[i] > biggest_size) { biggest_attno = i; biggest_size = toast_sizes[i]; } } if (biggest_attno < 0) break; /* * Attempt to compress it inline, if it has attstorage 'x' */ i = biggest_attno; if (att[i]->attstorage == 'x') { old_value = toast_values[i]; new_value = toast_compress_datum(old_value); if (DatumGetPointer(new_value) != NULL) { /* successful compression */ if (toast_free[i]) pfree(DatumGetPointer(old_value)); toast_values[i] = new_value; toast_free[i] = true; toast_sizes[i] = VARSIZE(DatumGetPointer(toast_values[i])); need_change = true; need_free = true; } else { /* incompressible, ignore on subsequent compression passes */ toast_action[i] = 'x'; } } else { /* has attstorage 'e', ignore on subsequent compression passes */ toast_action[i] = 'x'; } /* * If this value is by itself more than maxDataLen (after compression * if any), push it out to the toast table immediately, if possible. * This avoids uselessly compressing other fields in the common case * where we have one long field and several short ones. * * XXX maybe the threshold should be less than maxDataLen? */ if (toast_sizes[i] > maxDataLen && rel->rd_rel->reltoastrelid != InvalidOid) { old_value = toast_values[i]; toast_action[i] = 'p'; toast_values[i] = toast_save_datum(rel, toast_values[i], options); if (toast_free[i]) pfree(DatumGetPointer(old_value)); toast_free[i] = true; need_change = true; need_free = true; } } /* * Second we look for attributes of attstorage 'x' or 'e' that are still * inline. But skip this if there's no toast table to push them to. */ while (heap_compute_data_size(tupleDesc, toast_values, toast_isnull) > maxDataLen && rel->rd_rel->reltoastrelid != InvalidOid) { int biggest_attno = -1; int32 biggest_size = MAXALIGN(TOAST_POINTER_SIZE); Datum old_value; /*------ * Search for the biggest yet inlined attribute with * attstorage equals 'x' or 'e' *------ */ for (i = 0; i < numAttrs; i++) { if (toast_action[i] == 'p') continue; if (VARATT_IS_EXTERNAL(DatumGetPointer(toast_values[i]))) continue; /* can't happen, toast_action would be 'p' */ if (att[i]->attstorage != 'x' && att[i]->attstorage != 'e') continue; if (toast_sizes[i] > biggest_size) { biggest_attno = i; biggest_size = toast_sizes[i]; } } if (biggest_attno < 0) break; /* * Store this external */ i = biggest_attno; old_value = toast_values[i]; toast_action[i] = 'p'; toast_values[i] = toast_save_datum(rel, toast_values[i], options); if (toast_free[i]) pfree(DatumGetPointer(old_value)); toast_free[i] = true; need_change = true; need_free = true; } /* * Round 3 - this time we take attributes with storage 'm' into * compression */ while (heap_compute_data_size(tupleDesc, toast_values, toast_isnull) > maxDataLen) { int biggest_attno = -1; int32 biggest_size = MAXALIGN(TOAST_POINTER_SIZE); Datum old_value; Datum new_value; /* * Search for the biggest yet uncompressed internal attribute */ for (i = 0; i < numAttrs; i++) { if (toast_action[i] != ' ') continue; if (VARATT_IS_EXTERNAL(DatumGetPointer(toast_values[i]))) continue; /* can't happen, toast_action would be 'p' */ if (VARATT_IS_COMPRESSED(DatumGetPointer(toast_values[i]))) continue; if (att[i]->attstorage != 'm') continue; if (toast_sizes[i] > biggest_size) { biggest_attno = i; biggest_size = toast_sizes[i]; } } if (biggest_attno < 0) break; /* * Attempt to compress it inline */ i = biggest_attno; old_value = toast_values[i]; new_value = toast_compress_datum(old_value); if (DatumGetPointer(new_value) != NULL) { /* successful compression */ if (toast_free[i]) pfree(DatumGetPointer(old_value)); toast_values[i] = new_value; toast_free[i] = true; toast_sizes[i] = VARSIZE(DatumGetPointer(toast_values[i])); need_change = true; need_free = true; } else { /* incompressible, ignore on subsequent compression passes */ toast_action[i] = 'x'; } } /* * Finally we store attributes of type 'm' external, if possible. */ while (heap_compute_data_size(tupleDesc, toast_values, toast_isnull) > maxDataLen && rel->rd_rel->reltoastrelid != InvalidOid) { int biggest_attno = -1; int32 biggest_size = MAXALIGN(TOAST_POINTER_SIZE); Datum old_value; /*-------- * Search for the biggest yet inlined attribute with * attstorage = 'm' *-------- */ for (i = 0; i < numAttrs; i++) { if (toast_action[i] == 'p') continue; if (VARATT_IS_EXTERNAL(DatumGetPointer(toast_values[i]))) continue; /* can't happen, toast_action would be 'p' */ if (att[i]->attstorage != 'm') continue; if (toast_sizes[i] > biggest_size) { biggest_attno = i; biggest_size = toast_sizes[i]; } } if (biggest_attno < 0) break; /* * Store this external */ i = biggest_attno; old_value = toast_values[i]; toast_action[i] = 'p'; toast_values[i] = toast_save_datum(rel, toast_values[i], options); if (toast_free[i]) pfree(DatumGetPointer(old_value)); toast_free[i] = true; need_change = true; need_free = true; } /* * In the case we toasted any values, we need to build a new heap tuple * with the changed values. */ if (need_change) { HeapTupleHeader olddata = newtup->t_data; HeapTupleHeader new_data; int32 new_len; int32 new_data_len; /* * 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_data_len = heap_compute_data_size(tupleDesc, toast_values, toast_isnull); new_len += new_data_len; /* * Allocate and zero the space needed, and fill HeapTupleData fields. */ result_tuple = (HeapTuple) palloc0(HEAPTUPLESIZE + new_len); result_tuple->t_len = new_len; result_tuple->t_self = newtup->t_self; result_tuple->t_tableOid = newtup->t_tableOid; new_data = (HeapTupleHeader) ((char *) result_tuple + HEAPTUPLESIZE); result_tuple->t_data = new_data; /* * Put the existing tuple header and the changed values into place */ memcpy(new_data, olddata, olddata->t_hoff); heap_fill_tuple(tupleDesc, toast_values, toast_isnull, (char *) new_data + olddata->t_hoff, new_data_len, &(new_data->t_infomask), has_nulls ? new_data->t_bits : NULL); } else result_tuple = newtup; /* * Free allocated temp values */ if (need_free) for (i = 0; i < numAttrs; i++) if (toast_free[i]) pfree(DatumGetPointer(toast_values[i])); /* * Delete external values from the old tuple */ if (need_delold) for (i = 0; i < numAttrs; i++) if (toast_delold[i]) toast_delete_datum(rel, toast_oldvalues[i]); return result_tuple; }
static bytea * decrypt_internal(int is_pubenc, int need_text, text *data, text *key, text *keypsw, text *args) { int err; MBuf *src = NULL, *dst = NULL; uint8 tmp[VARHDRSZ]; uint8 *restmp; bytea *res; int res_len; PGP_Context *ctx = NULL; struct debug_expect ex; int got_unicode = 0; init_work(&ctx, need_text, args, &ex); src = mbuf_create_from_data((uint8 *) VARDATA(data), VARSIZE(data) - VARHDRSZ); dst = mbuf_create(VARSIZE(data) + 2048); /* * reserve room for header */ mbuf_append(dst, tmp, VARHDRSZ); /* * set key */ if (is_pubenc) { uint8 *psw = NULL; int psw_len = 0; MBuf *kbuf; if (keypsw) { psw = (uint8 *) VARDATA(keypsw); psw_len = VARSIZE(keypsw) - VARHDRSZ; } kbuf = create_mbuf_from_vardata(key); err = pgp_set_pubkey(ctx, kbuf, psw, psw_len, 1); mbuf_free(kbuf); } else err = pgp_set_symkey(ctx, (uint8 *) VARDATA(key), VARSIZE(key) - VARHDRSZ); /* * decrypt */ if (err >= 0) err = pgp_decrypt(ctx, src, dst); /* * failed? */ if (err < 0) goto out; if (ex.expect) check_expect(ctx, &ex); /* remember the setting */ got_unicode = pgp_get_unicode_mode(ctx); out: if (src) mbuf_free(src); if (ctx) pgp_free(ctx); if (err) { px_set_debug_handler(NULL); if (dst) mbuf_free(dst); ereport(ERROR, (errcode(ERRCODE_EXTERNAL_ROUTINE_INVOCATION_EXCEPTION), errmsg("%s", px_strerror(err)))); } res_len = mbuf_steal_data(dst, &restmp); mbuf_free(dst); /* res_len includes VARHDRSZ */ res = (bytea *) restmp; SET_VARSIZE(res, res_len); if (need_text && got_unicode) { text *utf = convert_from_utf8(res); if (utf != res) { clear_and_pfree(res); res = utf; } } px_set_debug_handler(NULL); /* * add successfull decryptions also into RNG */ add_entropy(res, key, keypsw); return res; }
/* * record_send - binary output routine for any composite type. */ Datum record_send(PG_FUNCTION_ARGS) { HeapTupleHeader rec = PG_GETARG_HEAPTUPLEHEADER(0); Oid tupType; int32 tupTypmod; TupleDesc tupdesc; HeapTupleData tuple; RecordIOData *my_extra; int ncolumns; int validcols; int i; Datum *values; char *nulls; StringInfoData buf; /* Extract type info from the tuple itself */ tupType = HeapTupleHeaderGetTypeId(rec); tupTypmod = HeapTupleHeaderGetTypMod(rec); tupdesc = lookup_rowtype_tupdesc(tupType, tupTypmod); ncolumns = tupdesc->natts; /* Build a temporary HeapTuple control structure */ tuple.t_len = HeapTupleHeaderGetDatumLength(rec); ItemPointerSetInvalid(&(tuple.t_self)); tuple.t_tableOid = InvalidOid; tuple.t_data = rec; /* * We arrange to look up the needed I/O info just once per series of * calls, assuming the record type doesn't change underneath us. */ my_extra = (RecordIOData *) fcinfo->flinfo->fn_extra; if (my_extra == NULL || my_extra->ncolumns != ncolumns) { fcinfo->flinfo->fn_extra = MemoryContextAlloc(fcinfo->flinfo->fn_mcxt, sizeof(RecordIOData) - sizeof(ColumnIOData) + ncolumns * sizeof(ColumnIOData)); my_extra = (RecordIOData *) fcinfo->flinfo->fn_extra; my_extra->record_type = InvalidOid; my_extra->record_typmod = 0; } if (my_extra->record_type != tupType || my_extra->record_typmod != tupTypmod) { MemSet(my_extra, 0, sizeof(RecordIOData) - sizeof(ColumnIOData) + ncolumns * sizeof(ColumnIOData)); my_extra->record_type = tupType; my_extra->record_typmod = tupTypmod; my_extra->ncolumns = ncolumns; } values = (Datum *) palloc(ncolumns * sizeof(Datum)); nulls = (char *) palloc(ncolumns * sizeof(char)); /* Break down the tuple into fields */ heap_deformtuple(&tuple, tupdesc, values, nulls); /* And build the result string */ pq_begintypsend(&buf); /* Need to scan to count nondeleted columns */ validcols = 0; for (i = 0; i < ncolumns; i++) { if (!tupdesc->attrs[i]->attisdropped) validcols++; } pq_sendint(&buf, validcols, 4); for (i = 0; i < ncolumns; i++) { ColumnIOData *column_info = &my_extra->columns[i]; Oid column_type = tupdesc->attrs[i]->atttypid; bytea *outputbytes; /* Ignore dropped columns in datatype */ if (tupdesc->attrs[i]->attisdropped) continue; pq_sendint(&buf, column_type, sizeof(Oid)); if (nulls[i] == 'n') { /* emit -1 data length to signify a NULL */ pq_sendint(&buf, -1, 4); continue; } /* * Convert the column value to binary */ if (column_info->column_type != column_type) { bool typIsVarlena; getTypeBinaryOutputInfo(column_type, &column_info->typiofunc, &typIsVarlena); fmgr_info_cxt(column_info->typiofunc, &column_info->proc, fcinfo->flinfo->fn_mcxt); column_info->column_type = column_type; } outputbytes = SendFunctionCall(&column_info->proc, values[i]); /* We assume the result will not have been toasted */ pq_sendint(&buf, VARSIZE(outputbytes) - VARHDRSZ, 4); pq_sendbytes(&buf, VARDATA(outputbytes), VARSIZE(outputbytes) - VARHDRSZ); pfree(outputbytes); } pfree(values); pfree(nulls); ReleaseTupleDesc(tupdesc); PG_RETURN_BYTEA_P(pq_endtypsend(&buf)); }
Datum bt_page_items_bytea(PG_FUNCTION_ARGS) { bytea *raw_page = PG_GETARG_BYTEA_P(0); Datum result; FuncCallContext *fctx; struct user_args *uargs; int raw_page_size; if (!superuser()) ereport(ERROR, (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), (errmsg("must be superuser to use pageinspect functions")))); if (SRF_IS_FIRSTCALL()) { BTPageOpaque opaque; MemoryContext mctx; TupleDesc tupleDesc; raw_page_size = VARSIZE(raw_page) - VARHDRSZ; if (raw_page_size < SizeOfPageHeaderData) ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), errmsg("input page too small (%d bytes)", raw_page_size))); fctx = SRF_FIRSTCALL_INIT(); mctx = MemoryContextSwitchTo(fctx->multi_call_memory_ctx); uargs = palloc(sizeof(struct user_args)); uargs->page = VARDATA(raw_page); uargs->offset = FirstOffsetNumber; opaque = (BTPageOpaque) PageGetSpecialPointer(uargs->page); if (P_ISMETA(opaque)) ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), errmsg("block is a meta page"))); if (P_ISDELETED(opaque)) elog(NOTICE, "page is deleted"); fctx->max_calls = PageGetMaxOffsetNumber(uargs->page); /* Build a tuple descriptor for our result type */ if (get_call_result_type(fcinfo, NULL, &tupleDesc) != TYPEFUNC_COMPOSITE) elog(ERROR, "return type must be a row type"); fctx->attinmeta = TupleDescGetAttInMetadata(tupleDesc); fctx->user_fctx = uargs; MemoryContextSwitchTo(mctx); } fctx = SRF_PERCALL_SETUP(); uargs = fctx->user_fctx; if (fctx->call_cntr < fctx->max_calls) { result = bt_page_print_tuples(fctx, uargs->page, uargs->offset); uargs->offset++; SRF_RETURN_NEXT(fctx, result); } else { pfree(uargs); SRF_RETURN_DONE(fctx); } }
/* ---------------- * printtup --- print a tuple in protocol 3.0 * ---------------- */ static bool printtup(TupleTableSlot *slot, DestReceiver *self) { TupleDesc typeinfo = slot->tts_tupleDescriptor; DR_printtup *myState = (DR_printtup *) self; MemoryContext oldcontext; StringInfo buf = &myState->buf; int natts = typeinfo->natts; int i; /* Set or update my derived attribute info, if needed */ if (myState->attrinfo != typeinfo || myState->nattrs != natts) printtup_prepare_info(myState, typeinfo, natts); /* Make sure the tuple is fully deconstructed */ slot_getallattrs(slot); /* Switch into per-row context so we can recover memory below */ oldcontext = MemoryContextSwitchTo(myState->tmpcontext); /* * Prepare a DataRow message (note buffer is in per-row context) */ pq_beginmessage_reuse(buf, 'D'); pq_sendint16(buf, natts); /* * send the attributes of this tuple */ for (i = 0; i < natts; ++i) { PrinttupAttrInfo *thisState = myState->myinfo + i; Datum attr = slot->tts_values[i]; if (slot->tts_isnull[i]) { pq_sendint32(buf, -1); continue; } /* * Here we catch undefined bytes in datums that are returned to the * client without hitting disk; see comments at the related check in * PageAddItem(). This test is most useful for uncompressed, * non-external datums, but we're quite likely to see such here when * testing new C functions. */ if (thisState->typisvarlena) VALGRIND_CHECK_MEM_IS_DEFINED(DatumGetPointer(attr), VARSIZE_ANY(attr)); if (thisState->format == 0) { /* Text output */ char *outputstr; outputstr = OutputFunctionCall(&thisState->finfo, attr); pq_sendcountedtext(buf, outputstr, strlen(outputstr), false); } else { /* Binary output */ bytea *outputbytes; outputbytes = SendFunctionCall(&thisState->finfo, attr); pq_sendint32(buf, VARSIZE(outputbytes) - VARHDRSZ); pq_sendbytes(buf, VARDATA(outputbytes), VARSIZE(outputbytes) - VARHDRSZ); } } pq_endmessage_reuse(buf); /* Return to caller's context, and flush row's temporary memory */ MemoryContextSwitchTo(oldcontext); MemoryContextReset(myState->tmpcontext); return true; }
/* * Check if a query reaction is equal on the product side to a predicate reaction by comparing all hashes. */ Datum reaction_equals (PG_FUNCTION_ARGS) { REACTION *query = PG_GETARG_REACTION_P (0); REACTION *predicate = PG_GETARG_REACTION_P (1); MOLECULE *tmpMol_q = NULL, *tmpMol_p = NULL; char *offset_q = MOLARRAYPTR(query); char *offset_p = MOLARRAYPTR(predicate); char *offset_q_products = NULL; int i,j,m=0;//,query_len = predicate->num_products+predicate->num_reactants; int4 q_num_products = query->num_products; int4 q_num_reactants = query->num_reactants; int4 p_num_products = predicate->num_products; int4 p_num_reactants = predicate->num_reactants; //int q_r_matches[p_num_reactants]; //int q_p_matches[p_num_products]; //int p_r_matches[p_num_reactants]; //int p_p_matches[p_num_products]; uint32 q_r_matches=0x0; uint32 q_p_matches=0x0; uint32 p_r_matches=0x0; uint32 p_p_matches=0x0; if(q_num_products > MAX_ELEM || p_num_products > MAX_ELEM || q_num_reactants > MAX_ELEM || p_num_reactants > MAX_ELEM) { elog (WARNING, "Only partial reaction matching"); if(q_num_products > MAX_ELEM) q_num_products = MAX_ELEM; if(p_num_products > MAX_ELEM) p_num_products = MAX_ELEM; if(q_num_reactants > MAX_ELEM) q_num_reactants = MAX_ELEM; if(p_num_reactants > MAX_ELEM) p_num_reactants = MAX_ELEM; } if(q_num_products != p_num_products || q_num_reactants != p_num_reactants) PG_RETURN_BOOL (false); //memset(&q_r_matches,0x0,q_num_reactants*sizeof(int)); //memset(&q_p_matches,0x0,q_num_products*sizeof(int)); //memset(&p_r_matches,0x0,p_num_reactants*sizeof(int)); //memset(&p_p_matches,0x0,p_num_products*sizeof(int)); if (q_num_reactants != 0) { for (i=0; i<p_num_reactants; i++) { offset_q = MOLARRAYPTR(query); tmpMol_p = (MOLECULE*) offset_p; for(j=0; j<q_num_reactants; j++) { tmpMol_q = (MOLECULE*) offset_q; if (memcmp (tmpMol_q->inchikey, tmpMol_p->inchikey, INCHIKEYSZ) == 0) { //p_r_matches[i]=1; //q_r_matches[j]=1; SET_BIT(p_r_matches,i); SET_BIT(q_r_matches,j); } offset_q+=VARSIZE(tmpMol_q)*sizeof(char); } offset_p+=VARSIZE(tmpMol_p)*sizeof(char); } } else { for (i=0; i<p_num_reactants; i++) { tmpMol_p = (MOLECULE*) offset_p; offset_p+=VARSIZE(tmpMol_p)*sizeof(char); //Must advance to products in any case } } for(i=0; i<p_num_reactants; i++) { //if(p_r_matches[i] == 1) j++; //m+=p_r_matches[i]; if(CHECK_BIT(p_r_matches,i)) m++; } for(i=0; i<q_num_reactants; i++) { //if(p_r_matches[i] == 1) j++; // m+=q_r_matches[i]; if(CHECK_BIT(q_r_matches,i)) m++; } if(m!=(q_num_reactants+p_num_reactants)) PG_RETURN_BOOL (false); offset_q_products = offset_q; if(q_num_products != 0) { for (i=0; i<p_num_products; i++) { offset_q = offset_q_products; tmpMol_p = (MOLECULE*) offset_p; for(j=0; j<q_num_products; j++) { tmpMol_q = (MOLECULE*) offset_q; if ( memcmp (tmpMol_p->inchikey, tmpMol_p->inchikey, INCHIKEYSZ) == 0) { //p_p_matches[i]=1; //q_p_matches[j]=1; SET_BIT(p_p_matches,i); SET_BIT(q_p_matches,j); } offset_q+=VARSIZE(tmpMol_q)*sizeof(char); } offset_p+=VARSIZE(tmpMol_p)*sizeof(char); } } m=0; for(i=0; i<p_num_products; i++) { //if(p_matches[i] == 1) j++; //m+=p_p_matches[i]; if(CHECK_BIT(p_p_matches,i)) m++; } for(i=0; i<q_num_products; i++) { //if(p_r_matches[i] == 1) j++; //m+=q_p_matches[i]; if(CHECK_BIT(q_p_matches,i)) m++; } if(m!=(q_num_products+p_num_products)) PG_RETURN_BOOL (false); PG_RETURN_BOOL (true); }
Datum gin_extract_jsonb_query(PG_FUNCTION_ARGS) { int32 *nentries = (int32 *) PG_GETARG_POINTER(1); StrategyNumber strategy = PG_GETARG_UINT16(2); int32 *searchMode = (int32 *) PG_GETARG_POINTER(6); Datum *entries; if (strategy == JsonbContainsStrategyNumber) { /* Query is a jsonb, so just apply gin_extract_jsonb... */ entries = (Datum *) DatumGetPointer(DirectFunctionCall2(gin_extract_jsonb, PG_GETARG_DATUM(0), PointerGetDatum(nentries))); /* ...although "contains {}" requires a full index scan */ if (*nentries == 0) *searchMode = GIN_SEARCH_MODE_ALL; } else if (strategy == JsonbExistsStrategyNumber) { /* Query is a text string, which we treat as a key */ text *query = PG_GETARG_TEXT_PP(0); *nentries = 1; entries = (Datum *) palloc(sizeof(Datum)); entries[0] = make_text_key(JGINFLAG_KEY, VARDATA_ANY(query), VARSIZE_ANY_EXHDR(query)); } else if (strategy == JsonbExistsAnyStrategyNumber || strategy == JsonbExistsAllStrategyNumber) { /* Query is a text array; each element is treated as a key */ ArrayType *query = PG_GETARG_ARRAYTYPE_P(0); Datum *key_datums; bool *key_nulls; int key_count; int i, j; deconstruct_array(query, TEXTOID, -1, false, 'i', &key_datums, &key_nulls, &key_count); entries = (Datum *) palloc(sizeof(Datum) * key_count); for (i = 0, j = 0; i < key_count; i++) { /* Nulls in the array are ignored */ if (key_nulls[i]) continue; entries[j++] = make_text_key(JGINFLAG_KEY, VARDATA(key_datums[i]), VARSIZE(key_datums[i]) - VARHDRSZ); } *nentries = j; /* ExistsAll with no keys should match everything */ if (j == 0 && strategy == JsonbExistsAllStrategyNumber) *searchMode = GIN_SEARCH_MODE_ALL; } else { elog(ERROR, "unrecognized strategy number: %d", strategy); entries = NULL; /* keep compiler quiet */ } PG_RETURN_POINTER(entries); }
inline static Datum rss_match(REACTION *query, REACTION *predicate) { MOLECULE *tmpMol_q = NULL, *tmpMol_p = NULL; char *offset_q = MOLARRAYPTR(query); char *offset_p = MOLARRAYPTR(predicate); char *offset_q_products = NULL; int i,j,m=0; int match; int4 q_num_products = query->num_products; int4 q_num_reactants = query->num_reactants; int4 p_num_products = predicate->num_products; int4 p_num_reactants = predicate->num_reactants; //int q_r_matches[p_num_reactants]; //int q_p_matches[p_num_products]; //int p_r_matches[p_num_reactants]; //int p_p_matches[p_num_products]; uint32 q_r_matches=0x0; uint32 q_p_matches=0x0; uint32 p_r_matches=0x0; uint32 p_p_matches=0x0; if(q_num_products > MAX_ELEM || p_num_products > MAX_ELEM || q_num_reactants > MAX_ELEM || p_num_reactants > MAX_ELEM) { elog (WARNING, "Only partial reaction matching"); if(q_num_products > MAX_ELEM) q_num_products = MAX_ELEM; if(p_num_products > MAX_ELEM) p_num_products = MAX_ELEM; if(q_num_reactants > MAX_ELEM) q_num_reactants = MAX_ELEM; if(p_num_reactants > MAX_ELEM) p_num_reactants = MAX_ELEM; } if(q_num_products > p_num_products || q_num_reactants > p_num_reactants) PG_RETURN_BOOL (false); if(q_num_products + p_num_products + q_num_reactants + p_num_reactants == 0) PG_RETURN_BOOL (true); if(q_num_products + q_num_reactants == 0) PG_RETURN_BOOL (false); //memset(&q_r_matches,0x0,q_num_reactants*sizeof(int)); //memset(&q_p_matches,0x0,q_num_products*sizeof(int)); //memset(&p_r_matches,0x0,p_num_reactants*sizeof(int)); //memset(&p_p_matches,0x0,p_num_products*sizeof(int)); if (q_num_reactants != 0) { for (i=0; i<p_num_reactants; i++) { offset_q = MOLARRAYPTR(query); tmpMol_p = (MOLECULE*) offset_p; for(j=0; j<q_num_reactants; j++) { tmpMol_q = (MOLECULE*) offset_q; if (tmpMol_q->disconnected == true) { match = ob_SSS (SMIPTR(tmpMol_q), SMIPTR(tmpMol_p)); } else { match = ob_SSS_SMARTS_native (SMIPTR(tmpMol_q), SMIPTR(tmpMol_p)); } if(match<0) elog (ERROR, "Invalid SMARTS pattern: %s",SMIPTR(tmpMol_q)); if ( match != 0) { //p_r_matches[i]=1; //q_r_matches[j]=1; SET_BIT(p_r_matches,i); SET_BIT(q_r_matches,j); } offset_q+=VARSIZE(tmpMol_q)*sizeof(char); } offset_p+=VARSIZE(tmpMol_p)*sizeof(char); } } else { for (i=0; i<p_num_reactants; i++) { tmpMol_p = (MOLECULE*) offset_p; offset_p+=VARSIZE(tmpMol_p)*sizeof(char); //Must advance to products in any case } } //j=0; for(i=0; i<p_num_reactants; i++) { //if(p_r_matches[i] == 1) j++; //m+=p_r_matches[i]; if(CHECK_BIT(p_r_matches,i)) m++; } for(i=0; i<q_num_reactants; i++) { //if(p_r_matches[i] == 1) j++; //m+=q_r_matches[i]; if(CHECK_BIT(q_r_matches,i)) m++; } if(m<(q_num_reactants*2)) PG_RETURN_BOOL (false); offset_q_products = offset_q; //offset_p = MOLARRAYPTR(predicate)+p_num_reactants*sizeof(MOLECULE*); if(q_num_products != 0) { for (i=0; i<p_num_products; i++) { //offset_q = MOLARRAYPTR(query); offset_q = offset_q_products; tmpMol_p = (MOLECULE*) offset_p; /*for(j=0;j<q_num_reactants;j++) { tmpMol_q = (MOLECULE*) offset_q; offset_q+=tmpMol_q->len*sizeof(char); } */ for(j=0; j<q_num_products; j++) { tmpMol_q = (MOLECULE*) offset_q; if (tmpMol_q->disconnected == true) { match = ob_SSS (SMIPTR(tmpMol_q), SMIPTR(tmpMol_p)); } else { match = ob_SSS_SMARTS_native (SMIPTR(tmpMol_q), SMIPTR(tmpMol_p)); } if(match<0) elog (ERROR, "Invalid SMARTS pattern: %s",SMIPTR(tmpMol_q)); if (match != 0) { //p_p_matches[i]=1; //q_p_matches[j]=1; SET_BIT(p_p_matches,i); SET_BIT(q_p_matches,j); } offset_q+=VARSIZE(tmpMol_q)*sizeof(char); } offset_p+=VARSIZE(tmpMol_p)*sizeof(char); } } m=0; for(i=0; i<p_num_products; i++) { //if(p_matches[i] == 1) j++; //m+=p_p_matches[i]; if(CHECK_BIT(p_p_matches,i)) m++; } for(i=0; i<q_num_products; i++) { //if(p_r_matches[i] == 1) j++; //m+=q_p_matches[i]; if(CHECK_BIT(q_p_matches,i)) m++; } if(m<(q_num_products*2)) PG_RETURN_BOOL (false); PG_RETURN_BOOL (true); }
/* ** GetPrepGeomCache ** ** Pull the current prepared geometry from the cache or make ** one if there is not one available. Only prepare geometry ** if we are seeing a key for the second time. That way rapidly ** cycling keys don't cause too much preparing. */ PrepGeomCache* GetPrepGeomCache(FunctionCallInfoData *fcinfo, GSERIALIZED *pg_geom1, GSERIALIZED *pg_geom2) { MemoryContext old_context; PrepGeomCache* cache = fcinfo->flinfo->fn_extra; int copy_keys = 1; size_t pg_geom1_size = 0; size_t pg_geom2_size = 0; /* Make sure this isn't someone else's cache object. */ if ( cache && cache->type != 2 ) cache = NULL; if (!PrepGeomHash) CreatePrepGeomHash(); if ( pg_geom1 ) pg_geom1_size = VARSIZE(pg_geom1) + VARHDRSZ; if ( pg_geom2 ) pg_geom2_size = VARSIZE(pg_geom2) + VARHDRSZ; if ( cache == NULL) { /* ** Cache requested, but the cache isn't set up yet. ** Set it up, but don't prepare the geometry yet. ** That way if the next call is a cache miss we haven't ** wasted time preparing a geometry we don't need. */ PrepGeomHashEntry pghe; old_context = MemoryContextSwitchTo(fcinfo->flinfo->fn_mcxt); cache = palloc(sizeof(PrepGeomCache)); MemoryContextSwitchTo(old_context); cache->type = 2; cache->prepared_geom = 0; cache->geom = 0; cache->argnum = 0; cache->pg_geom1 = 0; cache->pg_geom2 = 0; cache->pg_geom1_size = 0; cache->pg_geom2_size = 0; cache->context = MemoryContextCreate(T_AllocSetContext, 8192, &PreparedCacheContextMethods, fcinfo->flinfo->fn_mcxt, "PostGIS Prepared Geometry Context"); POSTGIS_DEBUGF(3, "GetPrepGeomCache: creating cache: %p", cache); pghe.context = cache->context; pghe.geom = 0; pghe.prepared_geom = 0; AddPrepGeomHashEntry( pghe ); fcinfo->flinfo->fn_extra = cache; POSTGIS_DEBUGF(3, "GetPrepGeomCache: adding context to hash: %p", cache); } else if ( pg_geom1 && cache->argnum != 2 && cache->pg_geom1_size == pg_geom1_size && memcmp(cache->pg_geom1, pg_geom1, pg_geom1_size) == 0) { if ( !cache->prepared_geom ) { /* ** Cache hit, but we haven't prepared our geometry yet. ** Prepare it. */ PrepGeomHashEntry* pghe; cache->geom = POSTGIS2GEOS( pg_geom1 ); cache->prepared_geom = GEOSPrepare( cache->geom ); cache->argnum = 1; POSTGIS_DEBUG(3, "GetPrepGeomCache: preparing obj in argument 1"); pghe = GetPrepGeomHashEntry(cache->context); pghe->geom = cache->geom; pghe->prepared_geom = cache->prepared_geom; POSTGIS_DEBUG(3, "GetPrepGeomCache: storing references to prepared obj in argument 1"); } else { /* ** Cache hit, and we're good to go. Do nothing. */ POSTGIS_DEBUG(3, "GetPrepGeomCache: cache hit, argument 1"); } /* We don't need new keys until we have a cache miss */ copy_keys = 0; } else if ( pg_geom2 && cache->argnum != 1 && cache->pg_geom2_size == pg_geom2_size && memcmp(cache->pg_geom2, pg_geom2, pg_geom2_size) == 0) { if ( !cache->prepared_geom ) { /* ** Cache hit on arg2, but we haven't prepared our geometry yet. ** Prepare it. */ PrepGeomHashEntry* pghe; cache->geom = POSTGIS2GEOS( pg_geom2 ); cache->prepared_geom = GEOSPrepare( cache->geom ); cache->argnum = 2; POSTGIS_DEBUG(3, "GetPrepGeomCache: preparing obj in argument 2"); pghe = GetPrepGeomHashEntry(cache->context); pghe->geom = cache->geom; pghe->prepared_geom = cache->prepared_geom; POSTGIS_DEBUG(3, "GetPrepGeomCache: storing references to prepared obj in argument 2"); } else { /* ** Cache hit, and we're good to go. Do nothing. */ POSTGIS_DEBUG(3, "GetPrepGeomCache: cache hit, argument 2"); } /* We don't need new keys until we have a cache miss */ copy_keys = 0; } else if ( cache->prepared_geom ) { /* ** No cache hits, so this must be a miss. ** Destroy the GEOS objects, empty the cache. */ PrepGeomHashEntry* pghe; pghe = GetPrepGeomHashEntry(cache->context); pghe->geom = 0; pghe->prepared_geom = 0; POSTGIS_DEBUGF(3, "GetPrepGeomCache: cache miss, argument %d", cache->argnum); GEOSPreparedGeom_destroy( cache->prepared_geom ); GEOSGeom_destroy( (GEOSGeometry *)cache->geom ); cache->prepared_geom = 0; cache->geom = 0; cache->argnum = 0; } if ( copy_keys && pg_geom1 ) { /* ** If this is a new key (cache miss) we flip into the function ** manager memory context and make a copy. We can't just store a pointer ** because this copy will be pfree'd at the end of this function ** call. */ POSTGIS_DEBUG(3, "GetPrepGeomCache: copying pg_geom1 into cache"); old_context = MemoryContextSwitchTo(fcinfo->flinfo->fn_mcxt); if ( cache->pg_geom1 ) pfree(cache->pg_geom1); cache->pg_geom1 = palloc(pg_geom1_size); MemoryContextSwitchTo(old_context); memcpy(cache->pg_geom1, pg_geom1, pg_geom1_size); cache->pg_geom1_size = pg_geom1_size; } if ( copy_keys && pg_geom2 ) { POSTGIS_DEBUG(3, "GetPrepGeomCache: copying pg_geom2 into cache"); old_context = MemoryContextSwitchTo(fcinfo->flinfo->fn_mcxt); if ( cache->pg_geom2 ) pfree(cache->pg_geom2); cache->pg_geom2 = palloc(pg_geom2_size); MemoryContextSwitchTo(old_context); memcpy(cache->pg_geom2, pg_geom2, pg_geom2_size); cache->pg_geom2_size = pg_geom2_size; } return cache; }
Datum hs_concat(PG_FUNCTION_ARGS) { HStore *s1 = PG_GETARG_HS(0); HStore *s2 = PG_GETARG_HS(1); HStore *out = palloc(VARSIZE(s1) + VARSIZE(s2)); char *ps1, *ps2, *pd; HEntry *es1, *es2, *ed; SET_VARSIZE(out, VARSIZE(s1) + VARSIZE(s2)); out->size = s1->size + s2->size; ps1 = STRPTR(s1); ps2 = STRPTR(s2); pd = STRPTR(out); es1 = ARRPTR(s1); es2 = ARRPTR(s2); ed = ARRPTR(out); while (es1 - ARRPTR(s1) < s1->size && es2 - ARRPTR(s2) < s2->size) { int difference; if (es1->keylen == es2->keylen) difference = strncmp(ps1, ps2, es1->keylen); else difference = (es1->keylen > es2->keylen) ? 1 : -1; if (difference == 0) { memcpy(ed, es2, sizeof(HEntry)); memcpy(pd, ps2, es2->keylen + ((es2->valisnull) ? 0 : es2->vallen)); ed->pos = pd - STRPTR(out); pd += es2->keylen + ((es2->valisnull) ? 0 : es2->vallen); ed++; ps1 += es1->keylen + ((es1->valisnull) ? 0 : es1->vallen); es1++; ps2 += es2->keylen + ((es2->valisnull) ? 0 : es2->vallen); es2++; } else if (difference > 0) { memcpy(ed, es2, sizeof(HEntry)); memcpy(pd, ps2, es2->keylen + ((es2->valisnull) ? 0 : es2->vallen)); ed->pos = pd - STRPTR(out); pd += es2->keylen + ((es2->valisnull) ? 0 : es2->vallen); ed++; ps2 += es2->keylen + ((es2->valisnull) ? 0 : es2->vallen); es2++; } else { memcpy(ed, es1, sizeof(HEntry)); memcpy(pd, ps1, es1->keylen + ((es1->valisnull) ? 0 : es1->vallen)); ed->pos = pd - STRPTR(out); pd += es1->keylen + ((es1->valisnull) ? 0 : es1->vallen); ed++; ps1 += es1->keylen + ((es1->valisnull) ? 0 : es1->vallen); es1++; } } while (es1 - ARRPTR(s1) < s1->size) { memcpy(ed, es1, sizeof(HEntry)); memcpy(pd, ps1, es1->keylen + ((es1->valisnull) ? 0 : es1->vallen)); ed->pos = pd - STRPTR(out); pd += es1->keylen + ((es1->valisnull) ? 0 : es1->vallen); ed++; ps1 += es1->keylen + ((es1->valisnull) ? 0 : es1->vallen); es1++; } while (es2 - ARRPTR(s2) < s2->size) { memcpy(ed, es2, sizeof(HEntry)); memcpy(pd, ps2, es2->keylen + ((es2->valisnull) ? 0 : es2->vallen)); ed->pos = pd - STRPTR(out); pd += es2->keylen + ((es2->valisnull) ? 0 : es2->vallen); ed++; ps2 += es2->keylen + ((es2->valisnull) ? 0 : es2->vallen); es2++; } if (ed - ARRPTR(out) != out->size) { int buflen = pd - STRPTR(out); pd = STRPTR(out); out->size = ed - ARRPTR(out); memmove(STRPTR(out), pd, buflen); SET_VARSIZE(out, CALCDATASIZE(out->size, buflen)); } PG_FREE_IF_COPY(s1, 0); PG_FREE_IF_COPY(s2, 1); PG_RETURN_POINTER(out); }