printtup.c 16.7 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
B
Bruce Momjian 已提交
12
 *	  $Header: /cvsroot/pgsql/src/backend/access/common/printtup.c,v 1.77 2003/08/04 02:39:56 momjian 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 96
	}
	self->pub.startup = printtup_startup;
	self->pub.shutdown = printtup_shutdown;
	self->pub.destroy = printtup_destroy;
	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 141 142 143 144 145 146
	{
		List	   *targetlist;

		if (portal->strategy == PORTAL_ONE_SELECT)
			targetlist = ((Query *) lfirst(portal->parseTrees))->targetList;
		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 179
{
	Form_pg_attribute *attrs = typeinfo->attrs;
	int			natts = typeinfo->natts;
	int			proto = PG_PROTOCOL_MAJOR(FrontendProtocol);
	int			i;
	StringInfoData buf;

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

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

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

				pq_sendint(&buf, res->resorigtbl, 4);
				pq_sendint(&buf, res->resorigcol, 2);
				targetlist = lnext(targetlist);
			}
			else
			{
				/* No info available, so send zeroes */
				pq_sendint(&buf, 0, 4);
				pq_sendint(&buf, 0, 2);
			}
211
		}
212 213 214 215 216 217 218 219 220
		/* 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));
221 222
		/* typmod appears in protocol 2.0 and up */
		if (proto >= 2)
223
			pq_sendint(&buf, atttypmod, sizeof(atttypmod));
224 225 226 227 228 229 230 231
		/* 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);
		}
232 233 234 235
	}
	pq_endmessage(&buf);
}

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

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

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

283
/* ----------------
284
 *		printtup --- print a tuple in protocol 3.0
285 286
 * ----------------
 */
287
static void
288
printtup(HeapTuple tuple, TupleDesc typeinfo, DestReceiver *self)
289 290 291
{
	DR_printtup *myState = (DR_printtup *) self;
	StringInfoData buf;
292
	int			natts = typeinfo->natts;
293 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
	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;
		}

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

332
		if (thisState->format == 0)
333
		{
334 335 336 337 338 339 340 341 342
			/* 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);
343 344 345
		}
		else
		{
346 347 348 349 350
			/* Binary output */
			bytea	   *outputbytes;

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

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

	pq_endmessage(&buf);
}

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

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

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

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

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

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

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

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

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

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

	pq_endmessage(&buf);
449 450
}

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

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

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

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

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

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

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

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

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

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

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

557 558 559 560 561
		pfree(value);

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

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

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

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

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

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

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

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

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

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

	pq_endmessage(&buf);
655
}