
pq_sendint() remains, so extension code doesn't unnecessarily break. Author: Andres Freund Discussion: https://postgr.es/m/20170914063418.sckdzgjfrsbekae4@alap3.anarazel.de
718 lines
19 KiB
C
718 lines
19 KiB
C
/*-------------------------------------------------------------------------
|
|
*
|
|
* printtup.c
|
|
* Routines to print out tuples to the destination (both frontend
|
|
* clients and standalone backends are supported here).
|
|
*
|
|
*
|
|
* Portions Copyright (c) 1996-2017, PostgreSQL Global Development Group
|
|
* Portions Copyright (c) 1994, Regents of the University of California
|
|
*
|
|
* IDENTIFICATION
|
|
* src/backend/access/common/printtup.c
|
|
*
|
|
*-------------------------------------------------------------------------
|
|
*/
|
|
#include "postgres.h"
|
|
|
|
#include "access/printtup.h"
|
|
#include "libpq/libpq.h"
|
|
#include "libpq/pqformat.h"
|
|
#include "tcop/pquery.h"
|
|
#include "utils/lsyscache.h"
|
|
#include "utils/memdebug.h"
|
|
#include "utils/memutils.h"
|
|
|
|
|
|
static void printtup_startup(DestReceiver *self, int operation,
|
|
TupleDesc typeinfo);
|
|
static bool printtup(TupleTableSlot *slot, DestReceiver *self);
|
|
static bool printtup_20(TupleTableSlot *slot, DestReceiver *self);
|
|
static bool printtup_internal_20(TupleTableSlot *slot, DestReceiver *self);
|
|
static void printtup_shutdown(DestReceiver *self);
|
|
static void printtup_destroy(DestReceiver *self);
|
|
|
|
static void SendRowDescriptionCols_2(StringInfo buf, TupleDesc typeinfo,
|
|
List *targetlist, int16 *formats);
|
|
static void SendRowDescriptionCols_3(StringInfo buf, TupleDesc typeinfo,
|
|
List *targetlist, int16 *formats);
|
|
|
|
/* ----------------------------------------------------------------
|
|
* printtup / debugtup support
|
|
* ----------------------------------------------------------------
|
|
*/
|
|
|
|
/* ----------------
|
|
* Private state for a printtup destination object
|
|
*
|
|
* NOTE: finfo is the lookup info for either typoutput or typsend, whichever
|
|
* we are using for this column.
|
|
* ----------------
|
|
*/
|
|
typedef struct
|
|
{ /* Per-attribute information */
|
|
Oid typoutput; /* Oid for the type's text output fn */
|
|
Oid typsend; /* Oid for the type's binary output fn */
|
|
bool typisvarlena; /* is it varlena (ie possibly toastable)? */
|
|
int16 format; /* format code for this column */
|
|
FmgrInfo finfo; /* Precomputed call info for output fn */
|
|
} PrinttupAttrInfo;
|
|
|
|
typedef struct
|
|
{
|
|
DestReceiver pub; /* publicly-known function pointers */
|
|
StringInfoData buf; /* output buffer */
|
|
Portal portal; /* the Portal we are printing from */
|
|
bool sendDescrip; /* send RowDescription at startup? */
|
|
TupleDesc attrinfo; /* The attr info we are set up for */
|
|
int nattrs;
|
|
PrinttupAttrInfo *myinfo; /* Cached info about each attr */
|
|
MemoryContext tmpcontext; /* Memory context for per-row workspace */
|
|
} DR_printtup;
|
|
|
|
/* ----------------
|
|
* Initialize: create a DestReceiver for printtup
|
|
* ----------------
|
|
*/
|
|
DestReceiver *
|
|
printtup_create_DR(CommandDest dest)
|
|
{
|
|
DR_printtup *self = (DR_printtup *) palloc0(sizeof(DR_printtup));
|
|
|
|
self->pub.receiveSlot = printtup; /* might get changed later */
|
|
self->pub.rStartup = printtup_startup;
|
|
self->pub.rShutdown = printtup_shutdown;
|
|
self->pub.rDestroy = printtup_destroy;
|
|
self->pub.mydest = dest;
|
|
|
|
/*
|
|
* Send T message automatically if DestRemote, but not if
|
|
* DestRemoteExecute
|
|
*/
|
|
self->sendDescrip = (dest == DestRemote);
|
|
|
|
self->attrinfo = NULL;
|
|
self->nattrs = 0;
|
|
self->myinfo = NULL;
|
|
self->tmpcontext = NULL;
|
|
|
|
return (DestReceiver *) self;
|
|
}
|
|
|
|
/*
|
|
* Set parameters for a DestRemote (or DestRemoteExecute) receiver
|
|
*/
|
|
void
|
|
SetRemoteDestReceiverParams(DestReceiver *self, Portal portal)
|
|
{
|
|
DR_printtup *myState = (DR_printtup *) self;
|
|
|
|
Assert(myState->pub.mydest == DestRemote ||
|
|
myState->pub.mydest == DestRemoteExecute);
|
|
|
|
myState->portal = portal;
|
|
|
|
if (PG_PROTOCOL_MAJOR(FrontendProtocol) < 3)
|
|
{
|
|
/*
|
|
* In protocol 2.0 the Bind message does not exist, so there is no way
|
|
* for the columns to have different print formats; it's sufficient to
|
|
* look at the first one.
|
|
*/
|
|
if (portal->formats && portal->formats[0] != 0)
|
|
myState->pub.receiveSlot = printtup_internal_20;
|
|
else
|
|
myState->pub.receiveSlot = printtup_20;
|
|
}
|
|
}
|
|
|
|
static void
|
|
printtup_startup(DestReceiver *self, int operation, TupleDesc typeinfo)
|
|
{
|
|
DR_printtup *myState = (DR_printtup *) self;
|
|
Portal portal = myState->portal;
|
|
|
|
/* create buffer to be used for all messages */
|
|
initStringInfo(&myState->buf);
|
|
|
|
/*
|
|
* Create a temporary memory context that we can reset once per row to
|
|
* recover palloc'd memory. This avoids any problems with leaks inside
|
|
* datatype output routines, and should be faster than retail pfree's
|
|
* anyway.
|
|
*/
|
|
myState->tmpcontext = AllocSetContextCreate(CurrentMemoryContext,
|
|
"printtup",
|
|
ALLOCSET_DEFAULT_SIZES);
|
|
|
|
if (PG_PROTOCOL_MAJOR(FrontendProtocol) < 3)
|
|
{
|
|
/*
|
|
* Send portal name to frontend (obsolete cruft, gone in proto 3.0)
|
|
*
|
|
* If portal name not specified, use "blank" portal.
|
|
*/
|
|
const char *portalName = portal->name;
|
|
|
|
if (portalName == NULL || portalName[0] == '\0')
|
|
portalName = "blank";
|
|
|
|
pq_puttextmessage('P', portalName);
|
|
}
|
|
|
|
/*
|
|
* If we are supposed to emit row descriptions, then send the tuple
|
|
* descriptor of the tuples.
|
|
*/
|
|
if (myState->sendDescrip)
|
|
SendRowDescriptionMessage(&myState->buf,
|
|
typeinfo,
|
|
FetchPortalTargetList(portal),
|
|
portal->formats);
|
|
|
|
/* ----------------
|
|
* We could set up the derived attr info at this time, but we postpone it
|
|
* until the first call of printtup, for 2 reasons:
|
|
* 1. We don't waste time (compared to the old way) if there are no
|
|
* tuples at all to output.
|
|
* 2. Checking in printtup allows us to handle the case that the tuples
|
|
* change type midway through (although this probably can't happen in
|
|
* the current executor).
|
|
* ----------------
|
|
*/
|
|
}
|
|
|
|
/*
|
|
* SendRowDescriptionMessage --- send a RowDescription message to the frontend
|
|
*
|
|
* Notes: the TupleDesc has typically been manufactured by ExecTypeFromTL()
|
|
* or some similar function; it does not contain a full set of fields.
|
|
* The targetlist will be NIL when executing a utility function that does
|
|
* not have a plan. If the targetlist isn't NIL then it is a Query node's
|
|
* targetlist; it is up to us to ignore resjunk columns in it. The formats[]
|
|
* array pointer might be NULL (if we are doing Describe on a prepared stmt);
|
|
* send zeroes for the format codes in that case.
|
|
*/
|
|
void
|
|
SendRowDescriptionMessage(StringInfo buf, TupleDesc typeinfo,
|
|
List *targetlist, int16 *formats)
|
|
{
|
|
int natts = typeinfo->natts;
|
|
int proto = PG_PROTOCOL_MAJOR(FrontendProtocol);
|
|
|
|
/* tuple descriptor message type */
|
|
pq_beginmessage_reuse(buf, 'T');
|
|
/* # of attrs in tuples */
|
|
pq_sendint16(buf, natts);
|
|
|
|
if (proto >= 3)
|
|
SendRowDescriptionCols_3(buf, typeinfo, targetlist, formats);
|
|
else
|
|
SendRowDescriptionCols_2(buf, typeinfo, targetlist, formats);
|
|
|
|
pq_endmessage_reuse(buf);
|
|
}
|
|
|
|
/*
|
|
* Send description for each column when using v3+ protocol
|
|
*/
|
|
static void
|
|
SendRowDescriptionCols_3(StringInfo buf, TupleDesc typeinfo, List *targetlist, int16 *formats)
|
|
{
|
|
int natts = typeinfo->natts;
|
|
int i;
|
|
ListCell *tlist_item = list_head(targetlist);
|
|
|
|
/*
|
|
* Preallocate memory for the entire message to be sent. That allows to
|
|
* use the significantly faster inline pqformat.h functions and to avoid
|
|
* reallocations.
|
|
*
|
|
* Have to overestimate the size of the column-names, to account for
|
|
* character set overhead.
|
|
*/
|
|
enlargeStringInfo(buf, (NAMEDATALEN * MAX_CONVERSION_GROWTH /* attname */
|
|
+ sizeof(Oid) /* resorigtbl */
|
|
+ sizeof(AttrNumber) /* resorigcol */
|
|
+ sizeof(Oid) /* atttypid */
|
|
+ sizeof(int16) /* attlen */
|
|
+ sizeof(int32) /* attypmod */
|
|
+ sizeof(int16) /* format */
|
|
) * natts);
|
|
|
|
for (i = 0; i < natts; ++i)
|
|
{
|
|
Form_pg_attribute att = TupleDescAttr(typeinfo, i);
|
|
Oid atttypid = att->atttypid;
|
|
int32 atttypmod = att->atttypmod;
|
|
Oid resorigtbl;
|
|
AttrNumber resorigcol;
|
|
int16 format;
|
|
|
|
/*
|
|
* If column is a domain, send the base type and typmod instead.
|
|
* Lookup before sending any ints, for efficiency.
|
|
*/
|
|
atttypid = getBaseTypeAndTypmod(atttypid, &atttypmod);
|
|
|
|
/* Do we have a non-resjunk tlist item? */
|
|
while (tlist_item &&
|
|
((TargetEntry *) lfirst(tlist_item))->resjunk)
|
|
tlist_item = lnext(tlist_item);
|
|
if (tlist_item)
|
|
{
|
|
TargetEntry *tle = (TargetEntry *) lfirst(tlist_item);
|
|
|
|
resorigtbl = tle->resorigtbl;
|
|
resorigcol = tle->resorigcol;
|
|
tlist_item = lnext(tlist_item);
|
|
}
|
|
else
|
|
{
|
|
/* No info available, so send zeroes */
|
|
resorigtbl = 0;
|
|
resorigcol = 0;
|
|
}
|
|
|
|
if (formats)
|
|
format = formats[i];
|
|
else
|
|
format = 0;
|
|
|
|
pq_writestring(buf, NameStr(att->attname));
|
|
pq_writeint32(buf, resorigtbl);
|
|
pq_writeint16(buf, resorigcol);
|
|
pq_writeint32(buf, atttypid);
|
|
pq_writeint16(buf, att->attlen);
|
|
pq_writeint32(buf, atttypmod);
|
|
pq_writeint16(buf, format);
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Send description for each column when using v2 protocol
|
|
*/
|
|
static void
|
|
SendRowDescriptionCols_2(StringInfo buf, TupleDesc typeinfo, List *targetlist, int16 *formats)
|
|
{
|
|
int natts = typeinfo->natts;
|
|
int i;
|
|
|
|
for (i = 0; i < natts; ++i)
|
|
{
|
|
Form_pg_attribute att = TupleDescAttr(typeinfo, i);
|
|
Oid atttypid = att->atttypid;
|
|
int32 atttypmod = att->atttypmod;
|
|
|
|
/* If column is a domain, send the base type and typmod instead */
|
|
atttypid = getBaseTypeAndTypmod(atttypid, &atttypmod);
|
|
|
|
pq_sendstring(buf, NameStr(att->attname));
|
|
/* column ID only info appears in protocol 3.0 and up */
|
|
pq_sendint32(buf, atttypid);
|
|
pq_sendint16(buf, att->attlen);
|
|
pq_sendint32(buf, atttypmod);
|
|
/* format info only appears in protocol 3.0 and up */
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Get the lookup info that printtup() needs
|
|
*/
|
|
static void
|
|
printtup_prepare_info(DR_printtup *myState, TupleDesc typeinfo, int numAttrs)
|
|
{
|
|
int16 *formats = myState->portal->formats;
|
|
int i;
|
|
|
|
/* get rid of any old data */
|
|
if (myState->myinfo)
|
|
pfree(myState->myinfo);
|
|
myState->myinfo = NULL;
|
|
|
|
myState->attrinfo = typeinfo;
|
|
myState->nattrs = numAttrs;
|
|
if (numAttrs <= 0)
|
|
return;
|
|
|
|
myState->myinfo = (PrinttupAttrInfo *)
|
|
palloc0(numAttrs * sizeof(PrinttupAttrInfo));
|
|
|
|
for (i = 0; i < numAttrs; i++)
|
|
{
|
|
PrinttupAttrInfo *thisState = myState->myinfo + i;
|
|
int16 format = (formats ? formats[i] : 0);
|
|
Form_pg_attribute attr = TupleDescAttr(typeinfo, i);
|
|
|
|
thisState->format = format;
|
|
if (format == 0)
|
|
{
|
|
getTypeOutputInfo(attr->atttypid,
|
|
&thisState->typoutput,
|
|
&thisState->typisvarlena);
|
|
fmgr_info(thisState->typoutput, &thisState->finfo);
|
|
}
|
|
else if (format == 1)
|
|
{
|
|
getTypeBinaryOutputInfo(attr->atttypid,
|
|
&thisState->typsend,
|
|
&thisState->typisvarlena);
|
|
fmgr_info(thisState->typsend, &thisState->finfo);
|
|
}
|
|
else
|
|
ereport(ERROR,
|
|
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
|
|
errmsg("unsupported format code: %d", format)));
|
|
}
|
|
}
|
|
|
|
/* ----------------
|
|
* 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;
|
|
}
|
|
|
|
/* ----------------
|
|
* printtup_20 --- print a tuple in protocol 2.0
|
|
* ----------------
|
|
*/
|
|
static bool
|
|
printtup_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 ASCII style)
|
|
*/
|
|
pq_beginmessage_reuse(buf, 'D');
|
|
|
|
/*
|
|
* 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];
|
|
char *outputstr;
|
|
|
|
if (slot->tts_isnull[i])
|
|
continue;
|
|
|
|
Assert(thisState->format == 0);
|
|
|
|
outputstr = OutputFunctionCall(&thisState->finfo, attr);
|
|
pq_sendcountedtext(buf, outputstr, strlen(outputstr), true);
|
|
}
|
|
|
|
pq_endmessage_reuse(buf);
|
|
|
|
/* Return to caller's context, and flush row's temporary memory */
|
|
MemoryContextSwitchTo(oldcontext);
|
|
MemoryContextReset(myState->tmpcontext);
|
|
|
|
return true;
|
|
}
|
|
|
|
/* ----------------
|
|
* printtup_shutdown
|
|
* ----------------
|
|
*/
|
|
static void
|
|
printtup_shutdown(DestReceiver *self)
|
|
{
|
|
DR_printtup *myState = (DR_printtup *) self;
|
|
|
|
if (myState->myinfo)
|
|
pfree(myState->myinfo);
|
|
myState->myinfo = NULL;
|
|
|
|
myState->attrinfo = NULL;
|
|
|
|
if (myState->tmpcontext)
|
|
MemoryContextDelete(myState->tmpcontext);
|
|
myState->tmpcontext = NULL;
|
|
}
|
|
|
|
/* ----------------
|
|
* printtup_destroy
|
|
* ----------------
|
|
*/
|
|
static void
|
|
printtup_destroy(DestReceiver *self)
|
|
{
|
|
pfree(self);
|
|
}
|
|
|
|
/* ----------------
|
|
* printatt
|
|
* ----------------
|
|
*/
|
|
static void
|
|
printatt(unsigned attributeId,
|
|
Form_pg_attribute attributeP,
|
|
char *value)
|
|
{
|
|
printf("\t%2d: %s%s%s%s\t(typeid = %u, len = %d, typmod = %d, byval = %c)\n",
|
|
attributeId,
|
|
NameStr(attributeP->attname),
|
|
value != NULL ? " = \"" : "",
|
|
value != NULL ? value : "",
|
|
value != NULL ? "\"" : "",
|
|
(unsigned int) (attributeP->atttypid),
|
|
attributeP->attlen,
|
|
attributeP->atttypmod,
|
|
attributeP->attbyval ? 't' : 'f');
|
|
}
|
|
|
|
/* ----------------
|
|
* debugStartup - prepare to print tuples for an interactive backend
|
|
* ----------------
|
|
*/
|
|
void
|
|
debugStartup(DestReceiver *self, int operation, TupleDesc typeinfo)
|
|
{
|
|
int natts = typeinfo->natts;
|
|
int i;
|
|
|
|
/*
|
|
* show the return type of the tuples
|
|
*/
|
|
for (i = 0; i < natts; ++i)
|
|
printatt((unsigned) i + 1, TupleDescAttr(typeinfo, i), NULL);
|
|
printf("\t----\n");
|
|
}
|
|
|
|
/* ----------------
|
|
* debugtup - print one tuple for an interactive backend
|
|
* ----------------
|
|
*/
|
|
bool
|
|
debugtup(TupleTableSlot *slot, DestReceiver *self)
|
|
{
|
|
TupleDesc typeinfo = slot->tts_tupleDescriptor;
|
|
int natts = typeinfo->natts;
|
|
int i;
|
|
Datum attr;
|
|
char *value;
|
|
bool isnull;
|
|
Oid typoutput;
|
|
bool typisvarlena;
|
|
|
|
for (i = 0; i < natts; ++i)
|
|
{
|
|
attr = slot_getattr(slot, i + 1, &isnull);
|
|
if (isnull)
|
|
continue;
|
|
getTypeOutputInfo(TupleDescAttr(typeinfo, i)->atttypid,
|
|
&typoutput, &typisvarlena);
|
|
|
|
value = OidOutputFunctionCall(typoutput, attr);
|
|
|
|
printatt((unsigned) i + 1, TupleDescAttr(typeinfo, i), value);
|
|
}
|
|
printf("\t----\n");
|
|
|
|
return true;
|
|
}
|
|
|
|
/* ----------------
|
|
* 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;
|
|
}
|