printtup.c 16.8 KB
Newer Older
1 2
/*-------------------------------------------------------------------------
 *
3
 * printtup.c
4
 *	  Routines to print out tuples to the destination (both frontend
5
 *	  clients and standalone backends are supported here).
6
 *
7
 *
B
Bruce Momjian 已提交
8
 * Portions Copyright (c) 1996-2003, PostgreSQL Global Development Group
B
Add:  
Bruce Momjian 已提交
9
 * Portions Copyright (c) 1994, Regents of the University of California
10 11
 *
 * IDENTIFICATION
12
 *	  $PostgreSQL: pgsql/src/backend/access/common/printtup.c,v 1.81 2004/05/26 04:41:03 neilc Exp $
13 14 15
 *
 *-------------------------------------------------------------------------
 */
16 17 18 19
#include "postgres.h"

#include "access/heapam.h"
#include "access/printtup.h"
20
#include "libpq/libpq.h"
21
#include "libpq/pqformat.h"
22
#include "utils/lsyscache.h"
23
#include "utils/portal.h"
24

M
Marc G. Fournier 已提交
25

26
static void printtup_startup(DestReceiver *self, int operation,
B
Bruce Momjian 已提交
27
				 TupleDesc typeinfo);
28
static void printtup(HeapTuple tuple, TupleDesc typeinfo,
B
Bruce Momjian 已提交
29
		 DestReceiver *self);
30
static void printtup_20(HeapTuple tuple, TupleDesc typeinfo,
B
Bruce Momjian 已提交
31
			DestReceiver *self);
32
static void printtup_internal_20(HeapTuple tuple, TupleDesc typeinfo,
B
Bruce Momjian 已提交
33
					 DestReceiver *self);
34 35 36
static void printtup_shutdown(DestReceiver *self);
static void printtup_destroy(DestReceiver *self);

37

38
/* ----------------------------------------------------------------
39
 *		printtup / debugtup support
40 41 42
 * ----------------------------------------------------------------
 */

43 44
/* ----------------
 *		Private state for a printtup destination object
45 46 47
 *
 * NOTE: finfo is the lookup info for either typoutput or typsend, whichever
 * we are using for this column.
48 49
 * ----------------
 */
B
Bruce Momjian 已提交
50 51
typedef struct
{								/* Per-attribute information */
52 53
	Oid			typoutput;		/* Oid for the type's text output fn */
	Oid			typsend;		/* Oid for the type's binary output fn */
54
	Oid			typelem;		/* typelem value to pass to the output fn */
55
	bool		typisvarlena;	/* is it varlena (ie possibly toastable)? */
56 57
	int16		format;			/* format code for this column */
	FmgrInfo	finfo;			/* Precomputed call info for output fn */
58
} PrinttupAttrInfo;
59

B
Bruce Momjian 已提交
60 61 62
typedef struct
{
	DestReceiver pub;			/* publicly-known function pointers */
63
	Portal		portal;			/* the Portal we are printing from */
64
	bool		sendDescrip;	/* send RowDescription at startup? */
B
Bruce Momjian 已提交
65 66 67
	TupleDesc	attrinfo;		/* The attr info we are set up for */
	int			nattrs;
	PrinttupAttrInfo *myinfo;	/* Cached info about each attr */
68
} DR_printtup;
69 70 71 72 73

/* ----------------
 *		Initialize: create a DestReceiver for printtup
 * ----------------
 */
B
Bruce Momjian 已提交
74
DestReceiver *
75
printtup_create_DR(CommandDest dest, Portal portal)
76
{
B
Bruce Momjian 已提交
77
	DR_printtup *self = (DR_printtup *) palloc(sizeof(DR_printtup));
78

79 80 81
	if (PG_PROTOCOL_MAJOR(FrontendProtocol) >= 3)
		self->pub.receiveTuple = printtup;
	else
82
	{
83
		/*
B
Bruce Momjian 已提交
84 85
		 * 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
86 87 88 89 90 91
		 * sufficient to look at the first one.
		 */
		if (portal->formats && portal->formats[0] != 0)
			self->pub.receiveTuple = printtup_internal_20;
		else
			self->pub.receiveTuple = printtup_20;
92
	}
93 94 95
	self->pub.rStartup = printtup_startup;
	self->pub.rShutdown = printtup_shutdown;
	self->pub.rDestroy = printtup_destroy;
96
	self->pub.mydest = dest;
97

98 99 100 101
	self->portal = portal;

	/* Send T message automatically if Remote, but not if RemoteExecute */
	self->sendDescrip = (dest == Remote);
102

103 104 105 106
	self->attrinfo = NULL;
	self->nattrs = 0;
	self->myinfo = NULL;

B
Bruce Momjian 已提交
107
	return (DestReceiver *) self;
108 109 110
}

static void
111
printtup_startup(DestReceiver *self, int operation, TupleDesc typeinfo)
112
{
113
	DR_printtup *myState = (DR_printtup *) self;
B
Bruce Momjian 已提交
114
	Portal		portal = myState->portal;
115

116 117 118
	if (PG_PROTOCOL_MAJOR(FrontendProtocol) < 3)
	{
		/*
B
Bruce Momjian 已提交
119 120
		 * Send portal name to frontend (obsolete cruft, gone in proto
		 * 3.0)
121 122 123
		 *
		 * If portal name not specified, use "blank" portal.
		 */
124 125 126
		const char *portalName = portal->name;

		if (portalName == NULL || portalName[0] == '\0')
127 128 129 130
			portalName = "blank";

		pq_puttextmessage('P', portalName);
	}
131 132

	/*
B
Bruce Momjian 已提交
133 134
	 * If this is a retrieve, and we are supposed to emit row
	 * descriptions, then we send back the tuple descriptor of the tuples.
135
	 */
136
	if (operation == CMD_SELECT && myState->sendDescrip)
137 138 139 140
	{
		List	   *targetlist;

		if (portal->strategy == PORTAL_ONE_SELECT)
141
			targetlist = ((Query *) linitial(portal->parseTrees))->targetList;
142 143 144 145 146
		else
			targetlist = NIL;

		SendRowDescriptionMessage(typeinfo, targetlist, portal->formats);
	}
147

148 149
	/* ----------------
	 * We could set up the derived attr info at this time, but we postpone it
150
	 * until the first call of printtup, for 2 reasons:
151
	 * 1. We don't waste time (compared to the old way) if there are no
B
Bruce Momjian 已提交
152
	 *	  tuples at all to output.
153
	 * 2. Checking in printtup allows us to handle the case that the tuples
B
Bruce Momjian 已提交
154 155
	 *	  change type midway through (although this probably can't happen in
	 *	  the current executor).
156 157 158 159
	 * ----------------
	 */
}

160 161
/*
 * SendRowDescriptionMessage --- send a RowDescription message to the frontend
162 163 164 165
 *
 * 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
166
 * not have a plan.  If the targetlist isn't NIL then it is a Query node's
B
Bruce Momjian 已提交
167
 * targetlist; it is up to us to ignore resjunk columns in it.	The formats[]
168 169
 * array pointer might be NULL (if we are doing Describe on a prepared stmt);
 * send zeroes for the format codes in that case.
170 171
 */
void
172
SendRowDescriptionMessage(TupleDesc typeinfo, List *targetlist, int16 *formats)
173 174 175 176 177 178
{
	Form_pg_attribute *attrs = typeinfo->attrs;
	int			natts = typeinfo->natts;
	int			proto = PG_PROTOCOL_MAJOR(FrontendProtocol);
	int			i;
	StringInfoData buf;
179
	ListCell   *tlist_item = list_head(targetlist);
180

B
Bruce Momjian 已提交
181 182
	pq_beginmessage(&buf, 'T'); /* tuple descriptor message type */
	pq_sendint(&buf, natts, 2); /* # of attrs in tuples */
183 184 185

	for (i = 0; i < natts; ++i)
	{
B
Bruce Momjian 已提交
186 187 188
		Oid			atttypid = attrs[i]->atttypid;
		int32		atttypmod = attrs[i]->atttypmod;
		Oid			basetype;
189

190 191 192 193
		pq_sendstring(&buf, NameStr(attrs[i]->attname));
		/* column ID info appears in protocol 3.0 and up */
		if (proto >= 3)
		{
194
			/* Do we have a non-resjunk tlist item? */
195 196 197 198
			while (tlist_item &&
				   ((TargetEntry *) lfirst(tlist_item))->resdom->resjunk)
				tlist_item = lnext(tlist_item);
			if (tlist_item)
199
			{
200
				Resdom	   *res = ((TargetEntry *) lfirst(tlist_item))->resdom;
201 202 203

				pq_sendint(&buf, res->resorigtbl, 4);
				pq_sendint(&buf, res->resorigcol, 2);
204
				tlist_item = lnext(tlist_item);
205 206 207 208 209 210 211
			}
			else
			{
				/* No info available, so send zeroes */
				pq_sendint(&buf, 0, 4);
				pq_sendint(&buf, 0, 2);
			}
212
		}
213 214 215 216 217 218 219 220 221
		/* If column is a domain, send the base type and typmod instead */
		basetype = getBaseType(atttypid);
		if (basetype != atttypid)
		{
			atttypmod = get_typtypmod(atttypid);
			atttypid = basetype;
		}
		pq_sendint(&buf, (int) atttypid, sizeof(atttypid));
		pq_sendint(&buf, attrs[i]->attlen, sizeof(attrs[i]->attlen));
222 223
		/* typmod appears in protocol 2.0 and up */
		if (proto >= 2)
224
			pq_sendint(&buf, atttypmod, sizeof(atttypmod));
225 226 227 228 229 230 231 232
		/* format info appears in protocol 3.0 and up */
		if (proto >= 3)
		{
			if (formats)
				pq_sendint(&buf, formats[i], 2);
			else
				pq_sendint(&buf, 0, 2);
		}
233 234 235 236
	}
	pq_endmessage(&buf);
}

237 238 239
/*
 * Get the lookup info that printtup() needs
 */
240
static void
241
printtup_prepare_info(DR_printtup *myState, TupleDesc typeinfo, int numAttrs)
242
{
243
	int16	   *formats = myState->portal->formats;
B
Bruce Momjian 已提交
244
	int			i;
245 246

	if (myState->myinfo)
B
Bruce Momjian 已提交
247
		pfree(myState->myinfo); /* get rid of any old data */
248 249 250 251 252
	myState->myinfo = NULL;
	myState->attrinfo = typeinfo;
	myState->nattrs = numAttrs;
	if (numAttrs <= 0)
		return;
B
Bruce Momjian 已提交
253
	myState->myinfo = (PrinttupAttrInfo *)
254
		palloc0(numAttrs * sizeof(PrinttupAttrInfo));
255 256
	for (i = 0; i < numAttrs; i++)
	{
B
Bruce Momjian 已提交
257
		PrinttupAttrInfo *thisState = myState->myinfo + i;
258
		int16		format = (formats ? formats[i] : 0);
B
Bruce Momjian 已提交
259

260 261 262 263 264 265 266
		thisState->format = format;
		if (format == 0)
		{
			getTypeOutputInfo(typeinfo->attrs[i]->atttypid,
							  &thisState->typoutput,
							  &thisState->typelem,
							  &thisState->typisvarlena);
267
			fmgr_info(thisState->typoutput, &thisState->finfo);
268 269 270 271 272 273 274 275 276 277
		}
		else if (format == 1)
		{
			getTypeBinaryOutputInfo(typeinfo->attrs[i]->atttypid,
									&thisState->typsend,
									&thisState->typelem,
									&thisState->typisvarlena);
			fmgr_info(thisState->typsend, &thisState->finfo);
		}
		else
278 279 280
			ereport(ERROR,
					(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
					 errmsg("unsupported format code: %d", format)));
281 282 283
	}
}

284
/* ----------------
285
 *		printtup --- print a tuple in protocol 3.0
286 287
 * ----------------
 */
288
static void
289
printtup(HeapTuple tuple, TupleDesc typeinfo, DestReceiver *self)
290 291 292
{
	DR_printtup *myState = (DR_printtup *) self;
	StringInfoData buf;
293
	int			natts = typeinfo->natts;
294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323
	int			i;

	/* Set or update my derived attribute info, if needed */
	if (myState->attrinfo != typeinfo || myState->nattrs != natts)
		printtup_prepare_info(myState, typeinfo, natts);

	/*
	 * 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,
					attr;
		bool		isnull;

		origattr = heap_getattr(tuple, i + 1, typeinfo, &isnull);
		if (isnull)
		{
			pq_sendint(&buf, -1, 4);
			continue;
		}

324
		/*
B
Bruce Momjian 已提交
325 326
		 * If we have a toasted datum, forcibly detoast it here to avoid
		 * memory leakage inside the type's output routine.
327 328 329 330 331
		 */
		if (thisState->typisvarlena)
			attr = PointerGetDatum(PG_DETOAST_DATUM(origattr));
		else
			attr = origattr;
332

333
		if (thisState->format == 0)
334
		{
335 336 337 338 339 340 341 342 343
			/* Text output */
			char	   *outputstr;

			outputstr = DatumGetCString(FunctionCall3(&thisState->finfo,
													  attr,
									ObjectIdGetDatum(thisState->typelem),
						  Int32GetDatum(typeinfo->attrs[i]->atttypmod)));
			pq_sendcountedtext(&buf, outputstr, strlen(outputstr), false);
			pfree(outputstr);
344 345 346
		}
		else
		{
347 348 349 350 351
			/* Binary output */
			bytea	   *outputbytes;

			outputbytes = DatumGetByteaP(FunctionCall2(&thisState->finfo,
													   attr,
B
Bruce Momjian 已提交
352
								  ObjectIdGetDatum(thisState->typelem)));
353 354 355 356 357
			/* 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);
358
		}
359 360 361 362

		/* Clean up detoasted copy, if any */
		if (attr != origattr)
			pfree(DatumGetPointer(attr));
363 364 365 366 367 368 369 370 371 372 373
	}

	pq_endmessage(&buf);
}

/* ----------------
 *		printtup_20 --- print a tuple in protocol 2.0
 * ----------------
 */
static void
printtup_20(HeapTuple tuple, TupleDesc typeinfo, DestReceiver *self)
374
{
B
Bruce Momjian 已提交
375
	DR_printtup *myState = (DR_printtup *) self;
376
	StringInfoData buf;
377
	int			natts = typeinfo->natts;
378 379
	int			i,
				j,
380
				k;
381

382
	/* Set or update my derived attribute info, if needed */
383 384
	if (myState->attrinfo != typeinfo || myState->nattrs != natts)
		printtup_prepare_info(myState, typeinfo, natts);
385

386 387
	/*
	 * tell the frontend to expect new tuple data (in ASCII style)
388
	 */
389
	pq_beginmessage(&buf, 'D');
390

391 392
	/*
	 * send a bitmap of which attributes are not null
393 394 395
	 */
	j = 0;
	k = 1 << 7;
396
	for (i = 0; i < natts; ++i)
397
	{
B
Bruce Momjian 已提交
398
		if (!heap_attisnull(tuple, i + 1))
399
			j |= k;				/* set bit if not null */
400
		k >>= 1;
401
		if (k == 0)				/* end of byte? */
402
		{
403
			pq_sendint(&buf, j, 1);
404 405 406
			j = 0;
			k = 1 << 7;
		}
407
	}
408
	if (k != (1 << 7))			/* flush last partial byte */
409
		pq_sendint(&buf, j, 1);
410

411 412
	/*
	 * send the attributes of this tuple
413
	 */
414
	for (i = 0; i < natts; ++i)
415
	{
B
Bruce Momjian 已提交
416
		PrinttupAttrInfo *thisState = myState->myinfo + i;
417 418 419 420
		Datum		origattr,
					attr;
		bool		isnull;
		char	   *outputstr;
B
Bruce Momjian 已提交
421

422
		origattr = heap_getattr(tuple, i + 1, typeinfo, &isnull);
M
 
Marc G. Fournier 已提交
423 424
		if (isnull)
			continue;
425

426 427 428
		Assert(thisState->format == 0);

		/*
B
Bruce Momjian 已提交
429 430
		 * If we have a toasted datum, forcibly detoast it here to avoid
		 * memory leakage inside the type's output routine.
431 432 433 434 435 436 437 438
		 */
		if (thisState->typisvarlena)
			attr = PointerGetDatum(PG_DETOAST_DATUM(origattr));
		else
			attr = origattr;

		outputstr = DatumGetCString(FunctionCall3(&thisState->finfo,
												  attr,
B
Bruce Momjian 已提交
439 440
									ObjectIdGetDatum(thisState->typelem),
						  Int32GetDatum(typeinfo->attrs[i]->atttypmod)));
441 442
		pq_sendcountedtext(&buf, outputstr, strlen(outputstr), true);
		pfree(outputstr);
443

444 445 446
		/* Clean up detoasted copy, if any */
		if (attr != origattr)
			pfree(DatumGetPointer(attr));
447
	}
448 449

	pq_endmessage(&buf);
450 451
}

452
/* ----------------
453
 *		printtup_shutdown
454 455 456
 * ----------------
 */
static void
457
printtup_shutdown(DestReceiver *self)
458
{
B
Bruce Momjian 已提交
459 460
	DR_printtup *myState = (DR_printtup *) self;

461 462
	if (myState->myinfo)
		pfree(myState->myinfo);
463 464 465 466 467 468 469 470 471 472 473 474
	myState->myinfo = NULL;
	myState->attrinfo = NULL;
}

/* ----------------
 *		printtup_destroy
 * ----------------
 */
static void
printtup_destroy(DestReceiver *self)
{
	pfree(self);
475 476
}

477
/* ----------------
478
 *		printatt
479 480 481 482
 * ----------------
 */
static void
printatt(unsigned attributeId,
483
		 Form_pg_attribute attributeP,
484
		 char *value)
485
{
B
Bruce Momjian 已提交
486
	printf("\t%2d: %s%s%s%s\t(typeid = %u, len = %d, typmod = %d, byval = %c)\n",
487
		   attributeId,
488
		   NameStr(attributeP->attname),
489 490 491 492 493
		   value != NULL ? " = \"" : "",
		   value != NULL ? value : "",
		   value != NULL ? "\"" : "",
		   (unsigned int) (attributeP->atttypid),
		   attributeP->attlen,
B
Bruce Momjian 已提交
494
		   attributeP->atttypmod,
495
		   attributeP->attbyval ? 't' : 'f');
496 497 498
}

/* ----------------
499
 *		debugStartup - prepare to print tuples for an interactive backend
500 501 502
 * ----------------
 */
void
503
debugStartup(DestReceiver *self, int operation, TupleDesc typeinfo)
504
{
505 506 507 508
	int			natts = typeinfo->natts;
	Form_pg_attribute *attinfo = typeinfo->attrs;
	int			i;

509 510 511
	/*
	 * show the return type of the tuples
	 */
512
	for (i = 0; i < natts; ++i)
513
		printatt((unsigned) i + 1, attinfo[i], NULL);
514
	printf("\t----\n");
515 516 517 518
}

/* ----------------
 *		debugtup - print one tuple for an interactive backend
519 520 521
 * ----------------
 */
void
522
debugtup(HeapTuple tuple, TupleDesc typeinfo, DestReceiver *self)
523
{
524
	int			natts = typeinfo->natts;
525
	int			i;
526 527
	Datum		origattr,
				attr;
528
	char	   *value;
529
	bool		isnull;
530 531
	Oid			typoutput,
				typelem;
532
	bool		typisvarlena;
533

534
	for (i = 0; i < natts; ++i)
535
	{
536
		origattr = heap_getattr(tuple, i + 1, typeinfo, &isnull);
537 538
		if (isnull)
			continue;
539 540
		getTypeOutputInfo(typeinfo->attrs[i]->atttypid,
						  &typoutput, &typelem, &typisvarlena);
B
Bruce Momjian 已提交
541

542
		/*
B
Bruce Momjian 已提交
543 544
		 * If we have a toasted datum, forcibly detoast it here to avoid
		 * memory leakage inside the type's output routine.
545 546 547 548 549
		 */
		if (typisvarlena)
			attr = PointerGetDatum(PG_DETOAST_DATUM(origattr));
		else
			attr = origattr;
550

551 552
		value = DatumGetCString(OidFunctionCall3(typoutput,
												 attr,
B
Bruce Momjian 已提交
553
											   ObjectIdGetDatum(typelem),
B
Bruce Momjian 已提交
554
						  Int32GetDatum(typeinfo->attrs[i]->atttypmod)));
555

556
		printatt((unsigned) i + 1, typeinfo->attrs[i], value);
557

558 559 560 561 562
		pfree(value);

		/* Clean up detoasted copy, if any */
		if (attr != origattr)
			pfree(DatumGetPointer(attr));
563
	}
564
	printf("\t----\n");
565 566 567
}

/* ----------------
568 569 570 571
 *		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.
572
 *
573
 * This is largely same as printtup_20, except we use binary formatting.
574 575
 * ----------------
 */
576
static void
577
printtup_internal_20(HeapTuple tuple, TupleDesc typeinfo, DestReceiver *self)
578
{
579
	DR_printtup *myState = (DR_printtup *) self;
580
	StringInfoData buf;
581
	int			natts = typeinfo->natts;
582 583 584
	int			i,
				j,
				k;
585 586 587 588

	/* Set or update my derived attribute info, if needed */
	if (myState->attrinfo != typeinfo || myState->nattrs != natts)
		printtup_prepare_info(myState, typeinfo, natts);
589

590 591
	/*
	 * tell the frontend to expect new tuple data (in binary style)
592
	 */
593
	pq_beginmessage(&buf, 'B');
594

595 596
	/*
	 * send a bitmap of which attributes are not null
597 598 599
	 */
	j = 0;
	k = 1 << 7;
600
	for (i = 0; i < natts; ++i)
601
	{
B
Bruce Momjian 已提交
602
		if (!heap_attisnull(tuple, i + 1))
603
			j |= k;				/* set bit if not null */
604
		k >>= 1;
605
		if (k == 0)				/* end of byte? */
606
		{
607
			pq_sendint(&buf, j, 1);
608 609 610
			j = 0;
			k = 1 << 7;
		}
611
	}
612
	if (k != (1 << 7))			/* flush last partial byte */
613
		pq_sendint(&buf, j, 1);
614

615 616
	/*
	 * send the attributes of this tuple
617
	 */
618
	for (i = 0; i < natts; ++i)
619
	{
620 621 622 623
		PrinttupAttrInfo *thisState = myState->myinfo + i;
		Datum		origattr,
					attr;
		bool		isnull;
624
		bytea	   *outputbytes;
625

626 627 628
		origattr = heap_getattr(tuple, i + 1, typeinfo, &isnull);
		if (isnull)
			continue;
629

630
		Assert(thisState->format == 1);
631

632
		/*
B
Bruce Momjian 已提交
633 634
		 * If we have a toasted datum, forcibly detoast it here to avoid
		 * memory leakage inside the type's output routine.
635 636 637
		 */
		if (thisState->typisvarlena)
			attr = PointerGetDatum(PG_DETOAST_DATUM(origattr));
638 639
		else
			attr = origattr;
640 641 642

		outputbytes = DatumGetByteaP(FunctionCall2(&thisState->finfo,
												   attr,
B
Bruce Momjian 已提交
643
								  ObjectIdGetDatum(thisState->typelem)));
644 645 646 647 648 649 650 651 652
		/* 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 (attr != origattr)
			pfree(DatumGetPointer(attr));
653
	}
654 655

	pq_endmessage(&buf);
656
}