mirror of
https://git.postgresql.org/git/postgresql.git
synced 2024-09-04 02:17:19 +02:00
maintained for each cache entry. A cache entry will not be freed until the matching ReleaseSysCache call has been executed. This eliminates worries about cache entries getting dropped while still in use. See my posting to pg-hackers of even date for more info.
428 lines
10 KiB
C
428 lines
10 KiB
C
/*-------------------------------------------------------------------------
|
|
*
|
|
* printtup.c
|
|
* Routines to print out tuples to the destination (binary or non-binary
|
|
* portals, frontend/interactive backend, etc.).
|
|
*
|
|
* Portions Copyright (c) 1996-2000, PostgreSQL, Inc
|
|
* Portions Copyright (c) 1994, Regents of the University of California
|
|
*
|
|
*
|
|
* IDENTIFICATION
|
|
* $Header: /cvsroot/pgsql/src/backend/access/common/printtup.c,v 1.54 2000/11/16 22:30:15 tgl Exp $
|
|
*
|
|
*-------------------------------------------------------------------------
|
|
*/
|
|
|
|
|
|
#include "postgres.h"
|
|
|
|
#include "access/heapam.h"
|
|
#include "access/printtup.h"
|
|
#include "catalog/pg_type.h"
|
|
#include "libpq/pqformat.h"
|
|
#include "utils/syscache.h"
|
|
|
|
static void printtup_setup(DestReceiver *self, TupleDesc typeinfo);
|
|
static void printtup(HeapTuple tuple, TupleDesc typeinfo, DestReceiver *self);
|
|
static void printtup_cleanup(DestReceiver *self);
|
|
|
|
/* ----------------------------------------------------------------
|
|
* printtup / debugtup support
|
|
* ----------------------------------------------------------------
|
|
*/
|
|
|
|
/* ----------------
|
|
* getTypeOutAndElem -- get both typoutput and typelem for a type
|
|
*
|
|
* We used to fetch these with two separate function calls,
|
|
* typtoout() and gettypelem(), which each called SearchSysCache.
|
|
* This way takes half the time.
|
|
* ----------------
|
|
*/
|
|
int
|
|
getTypeOutAndElem(Oid type, Oid *typOutput, Oid *typElem)
|
|
{
|
|
HeapTuple typeTuple;
|
|
Form_pg_type pt;
|
|
|
|
typeTuple = SearchSysCache(TYPEOID,
|
|
ObjectIdGetDatum(type),
|
|
0, 0, 0);
|
|
if (!HeapTupleIsValid(typeTuple))
|
|
elog(ERROR, "getTypeOutAndElem: Cache lookup of type %u failed", type);
|
|
pt = (Form_pg_type) GETSTRUCT(typeTuple);
|
|
|
|
*typOutput = pt->typoutput;
|
|
*typElem = pt->typelem;
|
|
ReleaseSysCache(typeTuple);
|
|
return OidIsValid(*typOutput);
|
|
}
|
|
|
|
/* ----------------
|
|
* Private state for a printtup destination object
|
|
* ----------------
|
|
*/
|
|
typedef struct
|
|
{ /* Per-attribute information */
|
|
Oid typoutput; /* Oid for the attribute's type output fn */
|
|
Oid typelem; /* typelem value to pass to the output fn */
|
|
FmgrInfo finfo; /* Precomputed call info for typoutput */
|
|
} PrinttupAttrInfo;
|
|
|
|
typedef struct
|
|
{
|
|
DestReceiver pub; /* publicly-known function pointers */
|
|
TupleDesc attrinfo; /* The attr info we are set up for */
|
|
int nattrs;
|
|
PrinttupAttrInfo *myinfo; /* Cached info about each attr */
|
|
} DR_printtup;
|
|
|
|
/* ----------------
|
|
* Initialize: create a DestReceiver for printtup
|
|
* ----------------
|
|
*/
|
|
DestReceiver *
|
|
printtup_create_DR()
|
|
{
|
|
DR_printtup *self = (DR_printtup *) palloc(sizeof(DR_printtup));
|
|
|
|
self->pub.receiveTuple = printtup;
|
|
self->pub.setup = printtup_setup;
|
|
self->pub.cleanup = printtup_cleanup;
|
|
|
|
self->attrinfo = NULL;
|
|
self->nattrs = 0;
|
|
self->myinfo = NULL;
|
|
|
|
return (DestReceiver *) self;
|
|
}
|
|
|
|
static void
|
|
printtup_setup(DestReceiver *self, TupleDesc typeinfo)
|
|
{
|
|
/* ----------------
|
|
* We could set up the derived attr info at this time, but we postpone it
|
|
* until the first call of printtup, for 3 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).
|
|
* 3. Right now, ExecutorRun passes a NULL for typeinfo anyway :-(
|
|
* ----------------
|
|
*/
|
|
}
|
|
|
|
static void
|
|
printtup_prepare_info(DR_printtup *myState, TupleDesc typeinfo, int numAttrs)
|
|
{
|
|
int i;
|
|
|
|
if (myState->myinfo)
|
|
pfree(myState->myinfo); /* get rid of any old data */
|
|
myState->myinfo = NULL;
|
|
myState->attrinfo = typeinfo;
|
|
myState->nattrs = numAttrs;
|
|
if (numAttrs <= 0)
|
|
return;
|
|
myState->myinfo = (PrinttupAttrInfo *)
|
|
palloc(numAttrs * sizeof(PrinttupAttrInfo));
|
|
for (i = 0; i < numAttrs; i++)
|
|
{
|
|
PrinttupAttrInfo *thisState = myState->myinfo + i;
|
|
|
|
if (getTypeOutAndElem((Oid) typeinfo->attrs[i]->atttypid,
|
|
&thisState->typoutput, &thisState->typelem))
|
|
fmgr_info(thisState->typoutput, &thisState->finfo);
|
|
}
|
|
}
|
|
|
|
/* ----------------
|
|
* printtup
|
|
* ----------------
|
|
*/
|
|
static void
|
|
printtup(HeapTuple tuple, TupleDesc typeinfo, DestReceiver *self)
|
|
{
|
|
DR_printtup *myState = (DR_printtup *) self;
|
|
StringInfoData buf;
|
|
int i,
|
|
j,
|
|
k;
|
|
char *outputstr;
|
|
Datum attr;
|
|
bool isnull;
|
|
|
|
/* Set or update my derived attribute info, if needed */
|
|
if (myState->attrinfo != typeinfo ||
|
|
myState->nattrs != tuple->t_data->t_natts)
|
|
printtup_prepare_info(myState, typeinfo, tuple->t_data->t_natts);
|
|
|
|
/* ----------------
|
|
* tell the frontend to expect new tuple data (in ASCII style)
|
|
* ----------------
|
|
*/
|
|
pq_beginmessage(&buf);
|
|
pq_sendbyte(&buf, 'D');
|
|
|
|
/* ----------------
|
|
* send a bitmap of which attributes are not null
|
|
* ----------------
|
|
*/
|
|
j = 0;
|
|
k = 1 << 7;
|
|
for (i = 0; i < tuple->t_data->t_natts; ++i)
|
|
{
|
|
if (!heap_attisnull(tuple, i + 1))
|
|
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 < tuple->t_data->t_natts; ++i)
|
|
{
|
|
PrinttupAttrInfo *thisState = myState->myinfo + i;
|
|
|
|
attr = heap_getattr(tuple, i + 1, typeinfo, &isnull);
|
|
if (isnull)
|
|
continue;
|
|
if (OidIsValid(thisState->typoutput))
|
|
{
|
|
outputstr = DatumGetCString(FunctionCall3(&thisState->finfo,
|
|
attr,
|
|
ObjectIdGetDatum(thisState->typelem),
|
|
Int32GetDatum(typeinfo->attrs[i]->atttypmod)));
|
|
pq_sendcountedtext(&buf, outputstr, strlen(outputstr));
|
|
pfree(outputstr);
|
|
}
|
|
else
|
|
{
|
|
outputstr = "<unprintable>";
|
|
pq_sendcountedtext(&buf, outputstr, strlen(outputstr));
|
|
}
|
|
}
|
|
|
|
pq_endmessage(&buf);
|
|
}
|
|
|
|
/* ----------------
|
|
* printtup_cleanup
|
|
* ----------------
|
|
*/
|
|
static void
|
|
printtup_cleanup(DestReceiver *self)
|
|
{
|
|
DR_printtup *myState = (DR_printtup *) self;
|
|
|
|
if (myState->myinfo)
|
|
pfree(myState->myinfo);
|
|
pfree(myState);
|
|
}
|
|
|
|
/* ----------------
|
|
* 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');
|
|
}
|
|
|
|
/* ----------------
|
|
* showatts
|
|
* ----------------
|
|
*/
|
|
void
|
|
showatts(char *name, TupleDesc tupleDesc)
|
|
{
|
|
int i;
|
|
int natts = tupleDesc->natts;
|
|
Form_pg_attribute *attinfo = tupleDesc->attrs;
|
|
|
|
puts(name);
|
|
for (i = 0; i < natts; ++i)
|
|
printatt((unsigned) i + 1, attinfo[i], (char *) NULL);
|
|
printf("\t----\n");
|
|
}
|
|
|
|
/* ----------------
|
|
* debugtup
|
|
* ----------------
|
|
*/
|
|
void
|
|
debugtup(HeapTuple tuple, TupleDesc typeinfo, DestReceiver *self)
|
|
{
|
|
int i;
|
|
Datum attr;
|
|
char *value;
|
|
bool isnull;
|
|
Oid typoutput,
|
|
typelem;
|
|
|
|
for (i = 0; i < tuple->t_data->t_natts; ++i)
|
|
{
|
|
attr = heap_getattr(tuple, i + 1, typeinfo, &isnull);
|
|
if (isnull)
|
|
continue;
|
|
if (getTypeOutAndElem((Oid) typeinfo->attrs[i]->atttypid,
|
|
&typoutput, &typelem))
|
|
{
|
|
value = DatumGetCString(OidFunctionCall3(typoutput,
|
|
attr,
|
|
ObjectIdGetDatum(typelem),
|
|
Int32GetDatum(typeinfo->attrs[i]->atttypmod)));
|
|
printatt((unsigned) i + 1, typeinfo->attrs[i], value);
|
|
pfree(value);
|
|
}
|
|
}
|
|
printf("\t----\n");
|
|
}
|
|
|
|
/* ----------------
|
|
* printtup_internal
|
|
* We use a different data prefix, e.g. 'B' instead of 'D' to
|
|
* indicate a tuple in internal (binary) form.
|
|
*
|
|
* This is same as printtup, except we don't use the typout func,
|
|
* and therefore have no need for persistent state.
|
|
* ----------------
|
|
*/
|
|
void
|
|
printtup_internal(HeapTuple tuple, TupleDesc typeinfo, DestReceiver *self)
|
|
{
|
|
StringInfoData buf;
|
|
int i,
|
|
j,
|
|
k;
|
|
Datum attr;
|
|
bool isnull;
|
|
|
|
/* ----------------
|
|
* tell the frontend to expect new tuple data (in binary style)
|
|
* ----------------
|
|
*/
|
|
pq_beginmessage(&buf);
|
|
pq_sendbyte(&buf, 'B');
|
|
|
|
/* ----------------
|
|
* send a bitmap of which attributes are not null
|
|
* ----------------
|
|
*/
|
|
j = 0;
|
|
k = 1 << 7;
|
|
for (i = 0; i < tuple->t_data->t_natts; ++i)
|
|
{
|
|
if (!heap_attisnull(tuple, i + 1))
|
|
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
|
|
* ----------------
|
|
*/
|
|
#ifdef IPORTAL_DEBUG
|
|
fprintf(stderr, "sending tuple with %d atts\n", tuple->t_data->t_natts);
|
|
#endif
|
|
for (i = 0; i < tuple->t_data->t_natts; ++i)
|
|
{
|
|
int32 len = typeinfo->attrs[i]->attlen;
|
|
|
|
attr = heap_getattr(tuple, i + 1, typeinfo, &isnull);
|
|
if (!isnull)
|
|
{
|
|
/* # of bytes, and opaque data */
|
|
if (len == -1)
|
|
{
|
|
/* variable length, assume a varlena structure */
|
|
len = VARSIZE(attr) - VARHDRSZ;
|
|
|
|
pq_sendint(&buf, len, VARHDRSZ);
|
|
pq_sendbytes(&buf, VARDATA(attr), len);
|
|
|
|
#ifdef IPORTAL_DEBUG
|
|
{
|
|
char *d = VARDATA(attr);
|
|
|
|
fprintf(stderr, "length %d data %x%x%x%x\n",
|
|
len, *d, *(d + 1), *(d + 2), *(d + 3));
|
|
}
|
|
#endif
|
|
}
|
|
else
|
|
{
|
|
/* fixed size */
|
|
if (typeinfo->attrs[i]->attbyval)
|
|
{
|
|
int8 i8;
|
|
int16 i16;
|
|
int32 i32;
|
|
|
|
pq_sendint(&buf, len, sizeof(int32));
|
|
switch (len)
|
|
{
|
|
case sizeof(int8):
|
|
i8 = DatumGetChar(attr);
|
|
pq_sendbytes(&buf, (char *) &i8, len);
|
|
break;
|
|
case sizeof(int16):
|
|
i16 = DatumGetInt16(attr);
|
|
pq_sendbytes(&buf, (char *) &i16, len);
|
|
break;
|
|
case sizeof(int32):
|
|
i32 = DatumGetInt32(attr);
|
|
pq_sendbytes(&buf, (char *) &i32, len);
|
|
break;
|
|
}
|
|
#ifdef IPORTAL_DEBUG
|
|
fprintf(stderr, "byval length %d data %d\n", len, attr);
|
|
#endif
|
|
}
|
|
else
|
|
{
|
|
pq_sendint(&buf, len, sizeof(int32));
|
|
pq_sendbytes(&buf, DatumGetPointer(attr), len);
|
|
#ifdef IPORTAL_DEBUG
|
|
fprintf(stderr, "byref length %d data %x\n", len,
|
|
DatumGetPointer(attr));
|
|
#endif
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
pq_endmessage(&buf);
|
|
}
|