1 /*-------------------------------------------------------------------------
4 * I/O functions for generic composite types.
6 * Portions Copyright (c) 1996-2006, PostgreSQL Global Development Group
7 * Portions Copyright (c) 1994, Regents of the University of California
11 * $PostgreSQL: pgsql/src/backend/utils/adt/rowtypes.c,v 1.15 2006/04/04 19:35:36 tgl Exp $
13 *-------------------------------------------------------------------------
19 #include "access/heapam.h"
20 #include "access/htup.h"
21 #include "catalog/pg_type.h"
22 #include "lib/stringinfo.h"
23 #include "libpq/pqformat.h"
24 #include "utils/builtins.h"
25 #include "utils/lsyscache.h"
26 #include "utils/typcache.h"
30 * structure to cache metadata needed for record I/O
32 typedef struct ColumnIOData
40 typedef struct RecordIOData
45 ColumnIOData columns[1]; /* VARIABLE LENGTH ARRAY */
50 * record_in - input routine for any composite type.
53 record_in(PG_FUNCTION_ARGS)
55 char *string = PG_GETARG_CSTRING(0);
56 Oid tupType = PG_GETARG_OID(1);
59 int32 typmod = PG_GETARG_INT32(2);
61 HeapTupleHeader result;
65 RecordIOData *my_extra;
66 bool needComma = false;
75 * Use the passed type unless it's RECORD; we can't support input of
76 * anonymous types, mainly because there's no good way to figure out which
77 * anonymous type is wanted. Note that for RECORD, what we'll probably
78 * actually get is RECORD's typelem, ie, zero.
80 if (tupType == InvalidOid || tupType == RECORDOID)
82 (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
83 errmsg("input of anonymous composite types is not implemented")));
84 tupTypmod = -1; /* for all non-anonymous types */
85 tupdesc = lookup_rowtype_tupdesc(tupType, tupTypmod);
86 ncolumns = tupdesc->natts;
89 * We arrange to look up the needed I/O info just once per series of
90 * calls, assuming the record type doesn't change underneath us.
92 my_extra = (RecordIOData *) fcinfo->flinfo->fn_extra;
93 if (my_extra == NULL ||
94 my_extra->ncolumns != ncolumns)
96 fcinfo->flinfo->fn_extra =
97 MemoryContextAlloc(fcinfo->flinfo->fn_mcxt,
98 sizeof(RecordIOData) - sizeof(ColumnIOData)
99 + ncolumns * sizeof(ColumnIOData));
100 my_extra = (RecordIOData *) fcinfo->flinfo->fn_extra;
101 my_extra->record_type = InvalidOid;
102 my_extra->record_typmod = 0;
105 if (my_extra->record_type != tupType ||
106 my_extra->record_typmod != tupTypmod)
109 sizeof(RecordIOData) - sizeof(ColumnIOData)
110 + ncolumns * sizeof(ColumnIOData));
111 my_extra->record_type = tupType;
112 my_extra->record_typmod = tupTypmod;
113 my_extra->ncolumns = ncolumns;
116 values = (Datum *) palloc(ncolumns * sizeof(Datum));
117 nulls = (char *) palloc(ncolumns * sizeof(char));
120 * Scan the string. We use "buf" to accumulate the de-quoted data for
121 * each column, which is then fed to the appropriate input converter.
124 /* Allow leading whitespace */
125 while (*ptr && isspace((unsigned char) *ptr))
129 (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
130 errmsg("malformed record literal: \"%s\"", string),
131 errdetail("Missing left parenthesis.")));
133 initStringInfo(&buf);
135 for (i = 0; i < ncolumns; i++)
137 ColumnIOData *column_info = &my_extra->columns[i];
138 Oid column_type = tupdesc->attrs[i]->atttypid;
141 /* Ignore dropped columns in datatype, but fill with nulls */
142 if (tupdesc->attrs[i]->attisdropped)
144 values[i] = (Datum) 0;
151 /* Skip comma that separates prior field from this one */
155 /* *ptr must be ')' */
157 (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
158 errmsg("malformed record literal: \"%s\"", string),
159 errdetail("Too few columns.")));
162 /* Check for null: completely empty input means null */
163 if (*ptr == ',' || *ptr == ')')
170 /* Extract string for this column */
171 bool inquote = false;
175 while (inquote || !(*ptr == ',' || *ptr == ')'))
181 (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
182 errmsg("malformed record literal: \"%s\"",
184 errdetail("Unexpected end of input.")));
189 (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
190 errmsg("malformed record literal: \"%s\"",
192 errdetail("Unexpected end of input.")));
193 appendStringInfoChar(&buf, *ptr++);
199 else if (*ptr == '\"')
201 /* doubled quote within quote sequence */
202 appendStringInfoChar(&buf, *ptr++);
208 appendStringInfoChar(&buf, ch);
211 column_data = buf.data;
216 * Convert the column value
218 if (column_info->column_type != column_type)
220 getTypeInputInfo(column_type,
221 &column_info->typiofunc,
222 &column_info->typioparam);
223 fmgr_info_cxt(column_info->typiofunc, &column_info->proc,
224 fcinfo->flinfo->fn_mcxt);
225 column_info->column_type = column_type;
228 values[i] = InputFunctionCall(&column_info->proc,
230 column_info->typioparam,
231 tupdesc->attrs[i]->atttypmod);
234 * Prep for next column
241 (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
242 errmsg("malformed record literal: \"%s\"", string),
243 errdetail("Too many columns.")));
244 /* Allow trailing whitespace */
245 while (*ptr && isspace((unsigned char) *ptr))
249 (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
250 errmsg("malformed record literal: \"%s\"", string),
251 errdetail("Junk after right parenthesis.")));
253 tuple = heap_formtuple(tupdesc, values, nulls);
256 * We cannot return tuple->t_data because heap_formtuple allocates it as
257 * part of a larger chunk, and our caller may expect to be able to pfree
258 * our result. So must copy the info into a new palloc chunk.
260 result = (HeapTupleHeader) palloc(tuple->t_len);
261 memcpy(result, tuple->t_data, tuple->t_len);
263 heap_freetuple(tuple);
268 PG_RETURN_HEAPTUPLEHEADER(result);
272 * record_out - output routine for any composite type.
275 record_out(PG_FUNCTION_ARGS)
277 HeapTupleHeader rec = PG_GETARG_HEAPTUPLEHEADER(0);
282 RecordIOData *my_extra;
283 bool needComma = false;
290 /* Extract type info from the tuple itself */
291 tupType = HeapTupleHeaderGetTypeId(rec);
292 tupTypmod = HeapTupleHeaderGetTypMod(rec);
293 tupdesc = lookup_rowtype_tupdesc(tupType, tupTypmod);
294 ncolumns = tupdesc->natts;
296 /* Build a temporary HeapTuple control structure */
297 tuple.t_len = HeapTupleHeaderGetDatumLength(rec);
298 ItemPointerSetInvalid(&(tuple.t_self));
299 tuple.t_tableOid = InvalidOid;
303 * We arrange to look up the needed I/O info just once per series of
304 * calls, assuming the record type doesn't change underneath us.
306 my_extra = (RecordIOData *) fcinfo->flinfo->fn_extra;
307 if (my_extra == NULL ||
308 my_extra->ncolumns != ncolumns)
310 fcinfo->flinfo->fn_extra =
311 MemoryContextAlloc(fcinfo->flinfo->fn_mcxt,
312 sizeof(RecordIOData) - sizeof(ColumnIOData)
313 + ncolumns * sizeof(ColumnIOData));
314 my_extra = (RecordIOData *) fcinfo->flinfo->fn_extra;
315 my_extra->record_type = InvalidOid;
316 my_extra->record_typmod = 0;
319 if (my_extra->record_type != tupType ||
320 my_extra->record_typmod != tupTypmod)
323 sizeof(RecordIOData) - sizeof(ColumnIOData)
324 + ncolumns * sizeof(ColumnIOData));
325 my_extra->record_type = tupType;
326 my_extra->record_typmod = tupTypmod;
327 my_extra->ncolumns = ncolumns;
330 values = (Datum *) palloc(ncolumns * sizeof(Datum));
331 nulls = (char *) palloc(ncolumns * sizeof(char));
333 /* Break down the tuple into fields */
334 heap_deformtuple(&tuple, tupdesc, values, nulls);
336 /* And build the result string */
337 initStringInfo(&buf);
339 appendStringInfoChar(&buf, '(');
341 for (i = 0; i < ncolumns; i++)
343 ColumnIOData *column_info = &my_extra->columns[i];
344 Oid column_type = tupdesc->attrs[i]->atttypid;
349 /* Ignore dropped columns in datatype */
350 if (tupdesc->attrs[i]->attisdropped)
354 appendStringInfoChar(&buf, ',');
359 /* emit nothing... */
364 * Convert the column value to text
366 if (column_info->column_type != column_type)
370 getTypeOutputInfo(column_type,
371 &column_info->typiofunc,
373 fmgr_info_cxt(column_info->typiofunc, &column_info->proc,
374 fcinfo->flinfo->fn_mcxt);
375 column_info->column_type = column_type;
378 value = OutputFunctionCall(&column_info->proc, values[i]);
380 /* Detect whether we need double quotes for this value */
381 nq = (value[0] == '\0'); /* force quotes for empty string */
382 for (tmp = value; *tmp; tmp++)
386 if (ch == '"' || ch == '\\' ||
387 ch == '(' || ch == ')' || ch == ',' ||
388 isspace((unsigned char) ch))
395 /* And emit the string */
397 appendStringInfoChar(&buf, '"');
398 for (tmp = value; *tmp; tmp++)
402 if (ch == '"' || ch == '\\')
403 appendStringInfoChar(&buf, ch);
404 appendStringInfoChar(&buf, ch);
407 appendStringInfoChar(&buf, '"');
410 appendStringInfoChar(&buf, ')');
415 PG_RETURN_CSTRING(buf.data);
419 * record_recv - binary input routine for any composite type.
422 record_recv(PG_FUNCTION_ARGS)
424 StringInfo buf = (StringInfo) PG_GETARG_POINTER(0);
425 Oid tupType = PG_GETARG_OID(1);
428 int32 typmod = PG_GETARG_INT32(2);
430 HeapTupleHeader result;
434 RecordIOData *my_extra;
443 * Use the passed type unless it's RECORD; we can't support input of
444 * anonymous types, mainly because there's no good way to figure out which
445 * anonymous type is wanted. Note that for RECORD, what we'll probably
446 * actually get is RECORD's typelem, ie, zero.
448 if (tupType == InvalidOid || tupType == RECORDOID)
450 (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
451 errmsg("input of anonymous composite types is not implemented")));
452 tupTypmod = -1; /* for all non-anonymous types */
453 tupdesc = lookup_rowtype_tupdesc(tupType, tupTypmod);
454 ncolumns = tupdesc->natts;
457 * We arrange to look up the needed I/O info just once per series of
458 * calls, assuming the record type doesn't change underneath us.
460 my_extra = (RecordIOData *) fcinfo->flinfo->fn_extra;
461 if (my_extra == NULL ||
462 my_extra->ncolumns != ncolumns)
464 fcinfo->flinfo->fn_extra =
465 MemoryContextAlloc(fcinfo->flinfo->fn_mcxt,
466 sizeof(RecordIOData) - sizeof(ColumnIOData)
467 + ncolumns * sizeof(ColumnIOData));
468 my_extra = (RecordIOData *) fcinfo->flinfo->fn_extra;
469 my_extra->record_type = InvalidOid;
470 my_extra->record_typmod = 0;
473 if (my_extra->record_type != tupType ||
474 my_extra->record_typmod != tupTypmod)
477 sizeof(RecordIOData) - sizeof(ColumnIOData)
478 + ncolumns * sizeof(ColumnIOData));
479 my_extra->record_type = tupType;
480 my_extra->record_typmod = tupTypmod;
481 my_extra->ncolumns = ncolumns;
484 values = (Datum *) palloc(ncolumns * sizeof(Datum));
485 nulls = (char *) palloc(ncolumns * sizeof(char));
487 /* Fetch number of columns user thinks it has */
488 usercols = pq_getmsgint(buf, 4);
490 /* Need to scan to count nondeleted columns */
492 for (i = 0; i < ncolumns; i++)
494 if (!tupdesc->attrs[i]->attisdropped)
497 if (usercols != validcols)
499 (errcode(ERRCODE_DATATYPE_MISMATCH),
500 errmsg("wrong number of columns: %d, expected %d",
501 usercols, validcols)));
503 /* Process each column */
504 for (i = 0; i < ncolumns; i++)
506 ColumnIOData *column_info = &my_extra->columns[i];
507 Oid column_type = tupdesc->attrs[i]->atttypid;
510 StringInfoData item_buf;
514 /* Ignore dropped columns in datatype, but fill with nulls */
515 if (tupdesc->attrs[i]->attisdropped)
517 values[i] = (Datum) 0;
522 /* Verify column datatype */
523 coltypoid = pq_getmsgint(buf, sizeof(Oid));
524 if (coltypoid != column_type)
526 (errcode(ERRCODE_DATATYPE_MISMATCH),
527 errmsg("wrong data type: %u, expected %u",
528 coltypoid, column_type)));
530 /* Get and check the item length */
531 itemlen = pq_getmsgint(buf, 4);
532 if (itemlen < -1 || itemlen > (buf->len - buf->cursor))
534 (errcode(ERRCODE_INVALID_BINARY_REPRESENTATION),
535 errmsg("insufficient data left in message")));
539 /* -1 length means NULL */
542 csave = 0; /* keep compiler quiet */
547 * Rather than copying data around, we just set up a phony
548 * StringInfo pointing to the correct portion of the input buffer.
549 * We assume we can scribble on the input buffer so as to maintain
550 * the convention that StringInfos have a trailing null.
552 item_buf.data = &buf->data[buf->cursor];
553 item_buf.maxlen = itemlen + 1;
554 item_buf.len = itemlen;
557 buf->cursor += itemlen;
559 csave = buf->data[buf->cursor];
560 buf->data[buf->cursor] = '\0';
566 /* Now call the column's receiveproc */
567 if (column_info->column_type != column_type)
569 getTypeBinaryInputInfo(column_type,
570 &column_info->typiofunc,
571 &column_info->typioparam);
572 fmgr_info_cxt(column_info->typiofunc, &column_info->proc,
573 fcinfo->flinfo->fn_mcxt);
574 column_info->column_type = column_type;
577 values[i] = ReceiveFunctionCall(&column_info->proc,
579 column_info->typioparam,
580 tupdesc->attrs[i]->atttypmod);
584 /* Trouble if it didn't eat the whole buffer */
585 if (item_buf.cursor != itemlen)
587 (errcode(ERRCODE_INVALID_BINARY_REPRESENTATION),
588 errmsg("improper binary format in record column %d",
591 buf->data[buf->cursor] = csave;
595 tuple = heap_formtuple(tupdesc, values, nulls);
598 * We cannot return tuple->t_data because heap_formtuple allocates it as
599 * part of a larger chunk, and our caller may expect to be able to pfree
600 * our result. So must copy the info into a new palloc chunk.
602 result = (HeapTupleHeader) palloc(tuple->t_len);
603 memcpy(result, tuple->t_data, tuple->t_len);
605 heap_freetuple(tuple);
609 PG_RETURN_HEAPTUPLEHEADER(result);
613 * record_send - binary output routine for any composite type.
616 record_send(PG_FUNCTION_ARGS)
618 HeapTupleHeader rec = PG_GETARG_HEAPTUPLEHEADER(0);
623 RecordIOData *my_extra;
631 /* Extract type info from the tuple itself */
632 tupType = HeapTupleHeaderGetTypeId(rec);
633 tupTypmod = HeapTupleHeaderGetTypMod(rec);
634 tupdesc = lookup_rowtype_tupdesc(tupType, tupTypmod);
635 ncolumns = tupdesc->natts;
637 /* Build a temporary HeapTuple control structure */
638 tuple.t_len = HeapTupleHeaderGetDatumLength(rec);
639 ItemPointerSetInvalid(&(tuple.t_self));
640 tuple.t_tableOid = InvalidOid;
644 * We arrange to look up the needed I/O info just once per series of
645 * calls, assuming the record type doesn't change underneath us.
647 my_extra = (RecordIOData *) fcinfo->flinfo->fn_extra;
648 if (my_extra == NULL ||
649 my_extra->ncolumns != ncolumns)
651 fcinfo->flinfo->fn_extra =
652 MemoryContextAlloc(fcinfo->flinfo->fn_mcxt,
653 sizeof(RecordIOData) - sizeof(ColumnIOData)
654 + ncolumns * sizeof(ColumnIOData));
655 my_extra = (RecordIOData *) fcinfo->flinfo->fn_extra;
656 my_extra->record_type = InvalidOid;
657 my_extra->record_typmod = 0;
660 if (my_extra->record_type != tupType ||
661 my_extra->record_typmod != tupTypmod)
664 sizeof(RecordIOData) - sizeof(ColumnIOData)
665 + ncolumns * sizeof(ColumnIOData));
666 my_extra->record_type = tupType;
667 my_extra->record_typmod = tupTypmod;
668 my_extra->ncolumns = ncolumns;
671 values = (Datum *) palloc(ncolumns * sizeof(Datum));
672 nulls = (char *) palloc(ncolumns * sizeof(char));
674 /* Break down the tuple into fields */
675 heap_deformtuple(&tuple, tupdesc, values, nulls);
677 /* And build the result string */
678 pq_begintypsend(&buf);
680 /* Need to scan to count nondeleted columns */
682 for (i = 0; i < ncolumns; i++)
684 if (!tupdesc->attrs[i]->attisdropped)
687 pq_sendint(&buf, validcols, 4);
689 for (i = 0; i < ncolumns; i++)
691 ColumnIOData *column_info = &my_extra->columns[i];
692 Oid column_type = tupdesc->attrs[i]->atttypid;
695 /* Ignore dropped columns in datatype */
696 if (tupdesc->attrs[i]->attisdropped)
699 pq_sendint(&buf, column_type, sizeof(Oid));
703 /* emit -1 data length to signify a NULL */
704 pq_sendint(&buf, -1, 4);
709 * Convert the column value to binary
711 if (column_info->column_type != column_type)
715 getTypeBinaryOutputInfo(column_type,
716 &column_info->typiofunc,
718 fmgr_info_cxt(column_info->typiofunc, &column_info->proc,
719 fcinfo->flinfo->fn_mcxt);
720 column_info->column_type = column_type;
723 outputbytes = SendFunctionCall(&column_info->proc, values[i]);
725 /* We assume the result will not have been toasted */
726 pq_sendint(&buf, VARSIZE(outputbytes) - VARHDRSZ, 4);
727 pq_sendbytes(&buf, VARDATA(outputbytes),
728 VARSIZE(outputbytes) - VARHDRSZ);
735 PG_RETURN_BYTEA_P(pq_endtypsend(&buf));