1 /*-------------------------------------------------------------------------
4 * I/O functions for generic composite types.
6 * Portions Copyright (c) 1996-2007, 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.19 2007/03/03 19:32:55 neilc Exp $
13 *-------------------------------------------------------------------------
19 #include "access/heapam.h"
20 #include "catalog/pg_type.h"
21 #include "libpq/pqformat.h"
22 #include "utils/builtins.h"
23 #include "utils/lsyscache.h"
24 #include "utils/typcache.h"
28 * structure to cache metadata needed for record I/O
30 typedef struct ColumnIOData
38 typedef struct RecordIOData
43 ColumnIOData columns[1]; /* VARIABLE LENGTH ARRAY */
48 * record_in - input routine for any composite type.
51 record_in(PG_FUNCTION_ARGS)
53 char *string = PG_GETARG_CSTRING(0);
54 Oid tupType = PG_GETARG_OID(1);
57 int32 typmod = PG_GETARG_INT32(2);
59 HeapTupleHeader result;
63 RecordIOData *my_extra;
64 bool needComma = false;
73 * Use the passed type unless it's RECORD; we can't support input of
74 * anonymous types, mainly because there's no good way to figure out which
75 * anonymous type is wanted. Note that for RECORD, what we'll probably
76 * actually get is RECORD's typelem, ie, zero.
78 if (tupType == InvalidOid || tupType == RECORDOID)
80 (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
81 errmsg("input of anonymous composite types is not implemented")));
82 tupTypmod = -1; /* for all non-anonymous types */
83 tupdesc = lookup_rowtype_tupdesc(tupType, tupTypmod);
84 ncolumns = tupdesc->natts;
87 * We arrange to look up the needed I/O info just once per series of
88 * calls, assuming the record type doesn't change underneath us.
90 my_extra = (RecordIOData *) fcinfo->flinfo->fn_extra;
91 if (my_extra == NULL ||
92 my_extra->ncolumns != ncolumns)
94 fcinfo->flinfo->fn_extra =
95 MemoryContextAlloc(fcinfo->flinfo->fn_mcxt,
96 sizeof(RecordIOData) - sizeof(ColumnIOData)
97 + ncolumns * sizeof(ColumnIOData));
98 my_extra = (RecordIOData *) fcinfo->flinfo->fn_extra;
99 my_extra->record_type = InvalidOid;
100 my_extra->record_typmod = 0;
103 if (my_extra->record_type != tupType ||
104 my_extra->record_typmod != tupTypmod)
107 sizeof(RecordIOData) - sizeof(ColumnIOData)
108 + ncolumns * sizeof(ColumnIOData));
109 my_extra->record_type = tupType;
110 my_extra->record_typmod = tupTypmod;
111 my_extra->ncolumns = ncolumns;
114 values = (Datum *) palloc(ncolumns * sizeof(Datum));
115 nulls = (char *) palloc(ncolumns * sizeof(char));
118 * Scan the string. We use "buf" to accumulate the de-quoted data for
119 * each column, which is then fed to the appropriate input converter.
122 /* Allow leading whitespace */
123 while (*ptr && isspace((unsigned char) *ptr))
127 (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
128 errmsg("malformed record literal: \"%s\"", string),
129 errdetail("Missing left parenthesis.")));
131 initStringInfo(&buf);
133 for (i = 0; i < ncolumns; i++)
135 ColumnIOData *column_info = &my_extra->columns[i];
136 Oid column_type = tupdesc->attrs[i]->atttypid;
139 /* Ignore dropped columns in datatype, but fill with nulls */
140 if (tupdesc->attrs[i]->attisdropped)
142 values[i] = (Datum) 0;
149 /* Skip comma that separates prior field from this one */
153 /* *ptr must be ')' */
155 (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
156 errmsg("malformed record literal: \"%s\"", string),
157 errdetail("Too few columns.")));
160 /* Check for null: completely empty input means null */
161 if (*ptr == ',' || *ptr == ')')
168 /* Extract string for this column */
169 bool inquote = false;
171 resetStringInfo(&buf);
172 while (inquote || !(*ptr == ',' || *ptr == ')'))
178 (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
179 errmsg("malformed record literal: \"%s\"",
181 errdetail("Unexpected end of input.")));
186 (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
187 errmsg("malformed record literal: \"%s\"",
189 errdetail("Unexpected end of input.")));
190 appendStringInfoChar(&buf, *ptr++);
196 else if (*ptr == '\"')
198 /* doubled quote within quote sequence */
199 appendStringInfoChar(&buf, *ptr++);
205 appendStringInfoChar(&buf, ch);
208 column_data = buf.data;
213 * Convert the column value
215 if (column_info->column_type != column_type)
217 getTypeInputInfo(column_type,
218 &column_info->typiofunc,
219 &column_info->typioparam);
220 fmgr_info_cxt(column_info->typiofunc, &column_info->proc,
221 fcinfo->flinfo->fn_mcxt);
222 column_info->column_type = column_type;
225 values[i] = InputFunctionCall(&column_info->proc,
227 column_info->typioparam,
228 tupdesc->attrs[i]->atttypmod);
231 * Prep for next column
238 (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
239 errmsg("malformed record literal: \"%s\"", string),
240 errdetail("Too many columns.")));
241 /* Allow trailing whitespace */
242 while (*ptr && isspace((unsigned char) *ptr))
246 (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
247 errmsg("malformed record literal: \"%s\"", string),
248 errdetail("Junk after right parenthesis.")));
250 tuple = heap_formtuple(tupdesc, values, nulls);
253 * We cannot return tuple->t_data because heap_formtuple allocates it as
254 * part of a larger chunk, and our caller may expect to be able to pfree
255 * our result. So must copy the info into a new palloc chunk.
257 result = (HeapTupleHeader) palloc(tuple->t_len);
258 memcpy(result, tuple->t_data, tuple->t_len);
260 heap_freetuple(tuple);
264 ReleaseTupleDesc(tupdesc);
266 PG_RETURN_HEAPTUPLEHEADER(result);
270 * record_out - output routine for any composite type.
273 record_out(PG_FUNCTION_ARGS)
275 HeapTupleHeader rec = PG_GETARG_HEAPTUPLEHEADER(0);
280 RecordIOData *my_extra;
281 bool needComma = false;
288 /* Extract type info from the tuple itself */
289 tupType = HeapTupleHeaderGetTypeId(rec);
290 tupTypmod = HeapTupleHeaderGetTypMod(rec);
291 tupdesc = lookup_rowtype_tupdesc(tupType, tupTypmod);
292 ncolumns = tupdesc->natts;
294 /* Build a temporary HeapTuple control structure */
295 tuple.t_len = HeapTupleHeaderGetDatumLength(rec);
296 ItemPointerSetInvalid(&(tuple.t_self));
297 tuple.t_tableOid = InvalidOid;
301 * We arrange to look up the needed I/O info just once per series of
302 * calls, assuming the record type doesn't change underneath us.
304 my_extra = (RecordIOData *) fcinfo->flinfo->fn_extra;
305 if (my_extra == NULL ||
306 my_extra->ncolumns != ncolumns)
308 fcinfo->flinfo->fn_extra =
309 MemoryContextAlloc(fcinfo->flinfo->fn_mcxt,
310 sizeof(RecordIOData) - sizeof(ColumnIOData)
311 + ncolumns * sizeof(ColumnIOData));
312 my_extra = (RecordIOData *) fcinfo->flinfo->fn_extra;
313 my_extra->record_type = InvalidOid;
314 my_extra->record_typmod = 0;
317 if (my_extra->record_type != tupType ||
318 my_extra->record_typmod != tupTypmod)
321 sizeof(RecordIOData) - sizeof(ColumnIOData)
322 + ncolumns * sizeof(ColumnIOData));
323 my_extra->record_type = tupType;
324 my_extra->record_typmod = tupTypmod;
325 my_extra->ncolumns = ncolumns;
328 values = (Datum *) palloc(ncolumns * sizeof(Datum));
329 nulls = (char *) palloc(ncolumns * sizeof(char));
331 /* Break down the tuple into fields */
332 heap_deformtuple(&tuple, tupdesc, values, nulls);
334 /* And build the result string */
335 initStringInfo(&buf);
337 appendStringInfoChar(&buf, '(');
339 for (i = 0; i < ncolumns; i++)
341 ColumnIOData *column_info = &my_extra->columns[i];
342 Oid column_type = tupdesc->attrs[i]->atttypid;
347 /* Ignore dropped columns in datatype */
348 if (tupdesc->attrs[i]->attisdropped)
352 appendStringInfoChar(&buf, ',');
357 /* emit nothing... */
362 * Convert the column value to text
364 if (column_info->column_type != column_type)
368 getTypeOutputInfo(column_type,
369 &column_info->typiofunc,
371 fmgr_info_cxt(column_info->typiofunc, &column_info->proc,
372 fcinfo->flinfo->fn_mcxt);
373 column_info->column_type = column_type;
376 value = OutputFunctionCall(&column_info->proc, values[i]);
378 /* Detect whether we need double quotes for this value */
379 nq = (value[0] == '\0'); /* force quotes for empty string */
380 for (tmp = value; *tmp; tmp++)
384 if (ch == '"' || ch == '\\' ||
385 ch == '(' || ch == ')' || ch == ',' ||
386 isspace((unsigned char) ch))
393 /* And emit the string */
395 appendStringInfoChar(&buf, '"');
396 for (tmp = value; *tmp; tmp++)
400 if (ch == '"' || ch == '\\')
401 appendStringInfoChar(&buf, ch);
402 appendStringInfoChar(&buf, ch);
405 appendStringInfoChar(&buf, '"');
408 appendStringInfoChar(&buf, ')');
412 ReleaseTupleDesc(tupdesc);
414 PG_RETURN_CSTRING(buf.data);
418 * record_recv - binary input routine for any composite type.
421 record_recv(PG_FUNCTION_ARGS)
423 StringInfo buf = (StringInfo) PG_GETARG_POINTER(0);
424 Oid tupType = PG_GETARG_OID(1);
427 int32 typmod = PG_GETARG_INT32(2);
429 HeapTupleHeader result;
433 RecordIOData *my_extra;
442 * Use the passed type unless it's RECORD; we can't support input of
443 * anonymous types, mainly because there's no good way to figure out which
444 * anonymous type is wanted. Note that for RECORD, what we'll probably
445 * actually get is RECORD's typelem, ie, zero.
447 if (tupType == InvalidOid || tupType == RECORDOID)
449 (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
450 errmsg("input of anonymous composite types is not implemented")));
451 tupTypmod = -1; /* for all non-anonymous types */
452 tupdesc = lookup_rowtype_tupdesc(tupType, tupTypmod);
453 ncolumns = tupdesc->natts;
456 * We arrange to look up the needed I/O info just once per series of
457 * calls, assuming the record type doesn't change underneath us.
459 my_extra = (RecordIOData *) fcinfo->flinfo->fn_extra;
460 if (my_extra == NULL ||
461 my_extra->ncolumns != ncolumns)
463 fcinfo->flinfo->fn_extra =
464 MemoryContextAlloc(fcinfo->flinfo->fn_mcxt,
465 sizeof(RecordIOData) - sizeof(ColumnIOData)
466 + ncolumns * sizeof(ColumnIOData));
467 my_extra = (RecordIOData *) fcinfo->flinfo->fn_extra;
468 my_extra->record_type = InvalidOid;
469 my_extra->record_typmod = 0;
472 if (my_extra->record_type != tupType ||
473 my_extra->record_typmod != tupTypmod)
476 sizeof(RecordIOData) - sizeof(ColumnIOData)
477 + ncolumns * sizeof(ColumnIOData));
478 my_extra->record_type = tupType;
479 my_extra->record_typmod = tupTypmod;
480 my_extra->ncolumns = ncolumns;
483 values = (Datum *) palloc(ncolumns * sizeof(Datum));
484 nulls = (char *) palloc(ncolumns * sizeof(char));
486 /* Fetch number of columns user thinks it has */
487 usercols = pq_getmsgint(buf, 4);
489 /* Need to scan to count nondeleted columns */
491 for (i = 0; i < ncolumns; i++)
493 if (!tupdesc->attrs[i]->attisdropped)
496 if (usercols != validcols)
498 (errcode(ERRCODE_DATATYPE_MISMATCH),
499 errmsg("wrong number of columns: %d, expected %d",
500 usercols, validcols)));
502 /* Process each column */
503 for (i = 0; i < ncolumns; i++)
505 ColumnIOData *column_info = &my_extra->columns[i];
506 Oid column_type = tupdesc->attrs[i]->atttypid;
509 StringInfoData item_buf;
513 /* Ignore dropped columns in datatype, but fill with nulls */
514 if (tupdesc->attrs[i]->attisdropped)
516 values[i] = (Datum) 0;
521 /* Verify column datatype */
522 coltypoid = pq_getmsgint(buf, sizeof(Oid));
523 if (coltypoid != column_type)
525 (errcode(ERRCODE_DATATYPE_MISMATCH),
526 errmsg("wrong data type: %u, expected %u",
527 coltypoid, column_type)));
529 /* Get and check the item length */
530 itemlen = pq_getmsgint(buf, 4);
531 if (itemlen < -1 || itemlen > (buf->len - buf->cursor))
533 (errcode(ERRCODE_INVALID_BINARY_REPRESENTATION),
534 errmsg("insufficient data left in message")));
538 /* -1 length means NULL */
541 csave = 0; /* keep compiler quiet */
546 * Rather than copying data around, we just set up a phony
547 * StringInfo pointing to the correct portion of the input buffer.
548 * We assume we can scribble on the input buffer so as to maintain
549 * the convention that StringInfos have a trailing null.
551 item_buf.data = &buf->data[buf->cursor];
552 item_buf.maxlen = itemlen + 1;
553 item_buf.len = itemlen;
556 buf->cursor += itemlen;
558 csave = buf->data[buf->cursor];
559 buf->data[buf->cursor] = '\0';
565 /* Now call the column's receiveproc */
566 if (column_info->column_type != column_type)
568 getTypeBinaryInputInfo(column_type,
569 &column_info->typiofunc,
570 &column_info->typioparam);
571 fmgr_info_cxt(column_info->typiofunc, &column_info->proc,
572 fcinfo->flinfo->fn_mcxt);
573 column_info->column_type = column_type;
576 values[i] = ReceiveFunctionCall(&column_info->proc,
578 column_info->typioparam,
579 tupdesc->attrs[i]->atttypmod);
583 /* Trouble if it didn't eat the whole buffer */
584 if (item_buf.cursor != itemlen)
586 (errcode(ERRCODE_INVALID_BINARY_REPRESENTATION),
587 errmsg("improper binary format in record column %d",
590 buf->data[buf->cursor] = csave;
594 tuple = heap_formtuple(tupdesc, values, nulls);
597 * We cannot return tuple->t_data because heap_formtuple allocates it as
598 * part of a larger chunk, and our caller may expect to be able to pfree
599 * our result. So must copy the info into a new palloc chunk.
601 result = (HeapTupleHeader) palloc(tuple->t_len);
602 memcpy(result, tuple->t_data, tuple->t_len);
604 heap_freetuple(tuple);
607 ReleaseTupleDesc(tupdesc);
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);
734 ReleaseTupleDesc(tupdesc);
736 PG_RETURN_BYTEA_P(pq_endtypsend(&buf));