1 /*-------------------------------------------------------------------------
4 * I/O functions for generic composite types.
6 * Portions Copyright (c) 1996-2008, 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.21 2008/05/12 00:00:51 alvherre Exp $
13 *-------------------------------------------------------------------------
19 #include "catalog/pg_type.h"
20 #include "libpq/pqformat.h"
21 #include "utils/builtins.h"
22 #include "utils/lsyscache.h"
23 #include "utils/typcache.h"
27 * structure to cache metadata needed for record I/O
29 typedef struct ColumnIOData
37 typedef struct RecordIOData
42 ColumnIOData columns[1]; /* VARIABLE LENGTH ARRAY */
47 * record_in - input routine for any composite type.
50 record_in(PG_FUNCTION_ARGS)
52 char *string = PG_GETARG_CSTRING(0);
53 Oid tupType = PG_GETARG_OID(1);
56 int32 typmod = PG_GETARG_INT32(2);
58 HeapTupleHeader result;
62 RecordIOData *my_extra;
63 bool needComma = false;
72 * Use the passed type unless it's RECORD; we can't support input of
73 * anonymous types, mainly because there's no good way to figure out which
74 * anonymous type is wanted. Note that for RECORD, what we'll probably
75 * actually get is RECORD's typelem, ie, zero.
77 if (tupType == InvalidOid || tupType == RECORDOID)
79 (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
80 errmsg("input of anonymous composite types is not implemented")));
81 tupTypmod = -1; /* for all non-anonymous types */
82 tupdesc = lookup_rowtype_tupdesc(tupType, tupTypmod);
83 ncolumns = tupdesc->natts;
86 * We arrange to look up the needed I/O info just once per series of
87 * calls, assuming the record type doesn't change underneath us.
89 my_extra = (RecordIOData *) fcinfo->flinfo->fn_extra;
90 if (my_extra == NULL ||
91 my_extra->ncolumns != ncolumns)
93 fcinfo->flinfo->fn_extra =
94 MemoryContextAlloc(fcinfo->flinfo->fn_mcxt,
95 sizeof(RecordIOData) - sizeof(ColumnIOData)
96 + ncolumns * sizeof(ColumnIOData));
97 my_extra = (RecordIOData *) fcinfo->flinfo->fn_extra;
98 my_extra->record_type = InvalidOid;
99 my_extra->record_typmod = 0;
102 if (my_extra->record_type != tupType ||
103 my_extra->record_typmod != tupTypmod)
106 sizeof(RecordIOData) - sizeof(ColumnIOData)
107 + ncolumns * sizeof(ColumnIOData));
108 my_extra->record_type = tupType;
109 my_extra->record_typmod = tupTypmod;
110 my_extra->ncolumns = ncolumns;
113 values = (Datum *) palloc(ncolumns * sizeof(Datum));
114 nulls = (char *) palloc(ncolumns * sizeof(char));
117 * Scan the string. We use "buf" to accumulate the de-quoted data for
118 * each column, which is then fed to the appropriate input converter.
121 /* Allow leading whitespace */
122 while (*ptr && isspace((unsigned char) *ptr))
126 (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
127 errmsg("malformed record literal: \"%s\"", string),
128 errdetail("Missing left parenthesis.")));
130 initStringInfo(&buf);
132 for (i = 0; i < ncolumns; i++)
134 ColumnIOData *column_info = &my_extra->columns[i];
135 Oid column_type = tupdesc->attrs[i]->atttypid;
138 /* Ignore dropped columns in datatype, but fill with nulls */
139 if (tupdesc->attrs[i]->attisdropped)
141 values[i] = (Datum) 0;
148 /* Skip comma that separates prior field from this one */
152 /* *ptr must be ')' */
154 (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
155 errmsg("malformed record literal: \"%s\"", string),
156 errdetail("Too few columns.")));
159 /* Check for null: completely empty input means null */
160 if (*ptr == ',' || *ptr == ')')
167 /* Extract string for this column */
168 bool inquote = false;
170 resetStringInfo(&buf);
171 while (inquote || !(*ptr == ',' || *ptr == ')'))
177 (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
178 errmsg("malformed record literal: \"%s\"",
180 errdetail("Unexpected end of input.")));
185 (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
186 errmsg("malformed record literal: \"%s\"",
188 errdetail("Unexpected end of input.")));
189 appendStringInfoChar(&buf, *ptr++);
195 else if (*ptr == '\"')
197 /* doubled quote within quote sequence */
198 appendStringInfoChar(&buf, *ptr++);
204 appendStringInfoChar(&buf, ch);
207 column_data = buf.data;
212 * Convert the column value
214 if (column_info->column_type != column_type)
216 getTypeInputInfo(column_type,
217 &column_info->typiofunc,
218 &column_info->typioparam);
219 fmgr_info_cxt(column_info->typiofunc, &column_info->proc,
220 fcinfo->flinfo->fn_mcxt);
221 column_info->column_type = column_type;
224 values[i] = InputFunctionCall(&column_info->proc,
226 column_info->typioparam,
227 tupdesc->attrs[i]->atttypmod);
230 * Prep for next column
237 (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
238 errmsg("malformed record literal: \"%s\"", string),
239 errdetail("Too many columns.")));
240 /* Allow trailing whitespace */
241 while (*ptr && isspace((unsigned char) *ptr))
245 (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
246 errmsg("malformed record literal: \"%s\"", string),
247 errdetail("Junk after right parenthesis.")));
249 tuple = heap_formtuple(tupdesc, values, nulls);
252 * We cannot return tuple->t_data because heap_formtuple allocates it as
253 * part of a larger chunk, and our caller may expect to be able to pfree
254 * our result. So must copy the info into a new palloc chunk.
256 result = (HeapTupleHeader) palloc(tuple->t_len);
257 memcpy(result, tuple->t_data, tuple->t_len);
259 heap_freetuple(tuple);
263 ReleaseTupleDesc(tupdesc);
265 PG_RETURN_HEAPTUPLEHEADER(result);
269 * record_out - output routine for any composite type.
272 record_out(PG_FUNCTION_ARGS)
274 HeapTupleHeader rec = PG_GETARG_HEAPTUPLEHEADER(0);
279 RecordIOData *my_extra;
280 bool needComma = false;
287 /* Extract type info from the tuple itself */
288 tupType = HeapTupleHeaderGetTypeId(rec);
289 tupTypmod = HeapTupleHeaderGetTypMod(rec);
290 tupdesc = lookup_rowtype_tupdesc(tupType, tupTypmod);
291 ncolumns = tupdesc->natts;
293 /* Build a temporary HeapTuple control structure */
294 tuple.t_len = HeapTupleHeaderGetDatumLength(rec);
295 ItemPointerSetInvalid(&(tuple.t_self));
296 tuple.t_tableOid = InvalidOid;
300 * We arrange to look up the needed I/O info just once per series of
301 * calls, assuming the record type doesn't change underneath us.
303 my_extra = (RecordIOData *) fcinfo->flinfo->fn_extra;
304 if (my_extra == NULL ||
305 my_extra->ncolumns != ncolumns)
307 fcinfo->flinfo->fn_extra =
308 MemoryContextAlloc(fcinfo->flinfo->fn_mcxt,
309 sizeof(RecordIOData) - sizeof(ColumnIOData)
310 + ncolumns * sizeof(ColumnIOData));
311 my_extra = (RecordIOData *) fcinfo->flinfo->fn_extra;
312 my_extra->record_type = InvalidOid;
313 my_extra->record_typmod = 0;
316 if (my_extra->record_type != tupType ||
317 my_extra->record_typmod != tupTypmod)
320 sizeof(RecordIOData) - sizeof(ColumnIOData)
321 + ncolumns * sizeof(ColumnIOData));
322 my_extra->record_type = tupType;
323 my_extra->record_typmod = tupTypmod;
324 my_extra->ncolumns = ncolumns;
327 values = (Datum *) palloc(ncolumns * sizeof(Datum));
328 nulls = (char *) palloc(ncolumns * sizeof(char));
330 /* Break down the tuple into fields */
331 heap_deformtuple(&tuple, tupdesc, values, nulls);
333 /* And build the result string */
334 initStringInfo(&buf);
336 appendStringInfoChar(&buf, '(');
338 for (i = 0; i < ncolumns; i++)
340 ColumnIOData *column_info = &my_extra->columns[i];
341 Oid column_type = tupdesc->attrs[i]->atttypid;
346 /* Ignore dropped columns in datatype */
347 if (tupdesc->attrs[i]->attisdropped)
351 appendStringInfoChar(&buf, ',');
356 /* emit nothing... */
361 * Convert the column value to text
363 if (column_info->column_type != column_type)
367 getTypeOutputInfo(column_type,
368 &column_info->typiofunc,
370 fmgr_info_cxt(column_info->typiofunc, &column_info->proc,
371 fcinfo->flinfo->fn_mcxt);
372 column_info->column_type = column_type;
375 value = OutputFunctionCall(&column_info->proc, values[i]);
377 /* Detect whether we need double quotes for this value */
378 nq = (value[0] == '\0'); /* force quotes for empty string */
379 for (tmp = value; *tmp; tmp++)
383 if (ch == '"' || ch == '\\' ||
384 ch == '(' || ch == ')' || ch == ',' ||
385 isspace((unsigned char) ch))
392 /* And emit the string */
394 appendStringInfoChar(&buf, '"');
395 for (tmp = value; *tmp; tmp++)
399 if (ch == '"' || ch == '\\')
400 appendStringInfoChar(&buf, ch);
401 appendStringInfoChar(&buf, ch);
404 appendStringInfoChar(&buf, '"');
407 appendStringInfoChar(&buf, ')');
411 ReleaseTupleDesc(tupdesc);
413 PG_RETURN_CSTRING(buf.data);
417 * record_recv - binary input routine for any composite type.
420 record_recv(PG_FUNCTION_ARGS)
422 StringInfo buf = (StringInfo) PG_GETARG_POINTER(0);
423 Oid tupType = PG_GETARG_OID(1);
426 int32 typmod = PG_GETARG_INT32(2);
428 HeapTupleHeader result;
432 RecordIOData *my_extra;
441 * Use the passed type unless it's RECORD; we can't support input of
442 * anonymous types, mainly because there's no good way to figure out which
443 * anonymous type is wanted. Note that for RECORD, what we'll probably
444 * actually get is RECORD's typelem, ie, zero.
446 if (tupType == InvalidOid || tupType == RECORDOID)
448 (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
449 errmsg("input of anonymous composite types is not implemented")));
450 tupTypmod = -1; /* for all non-anonymous types */
451 tupdesc = lookup_rowtype_tupdesc(tupType, tupTypmod);
452 ncolumns = tupdesc->natts;
455 * We arrange to look up the needed I/O info just once per series of
456 * calls, assuming the record type doesn't change underneath us.
458 my_extra = (RecordIOData *) fcinfo->flinfo->fn_extra;
459 if (my_extra == NULL ||
460 my_extra->ncolumns != ncolumns)
462 fcinfo->flinfo->fn_extra =
463 MemoryContextAlloc(fcinfo->flinfo->fn_mcxt,
464 sizeof(RecordIOData) - sizeof(ColumnIOData)
465 + ncolumns * sizeof(ColumnIOData));
466 my_extra = (RecordIOData *) fcinfo->flinfo->fn_extra;
467 my_extra->record_type = InvalidOid;
468 my_extra->record_typmod = 0;
471 if (my_extra->record_type != tupType ||
472 my_extra->record_typmod != tupTypmod)
475 sizeof(RecordIOData) - sizeof(ColumnIOData)
476 + ncolumns * sizeof(ColumnIOData));
477 my_extra->record_type = tupType;
478 my_extra->record_typmod = tupTypmod;
479 my_extra->ncolumns = ncolumns;
482 values = (Datum *) palloc(ncolumns * sizeof(Datum));
483 nulls = (char *) palloc(ncolumns * sizeof(char));
485 /* Fetch number of columns user thinks it has */
486 usercols = pq_getmsgint(buf, 4);
488 /* Need to scan to count nondeleted columns */
490 for (i = 0; i < ncolumns; i++)
492 if (!tupdesc->attrs[i]->attisdropped)
495 if (usercols != validcols)
497 (errcode(ERRCODE_DATATYPE_MISMATCH),
498 errmsg("wrong number of columns: %d, expected %d",
499 usercols, validcols)));
501 /* Process each column */
502 for (i = 0; i < ncolumns; i++)
504 ColumnIOData *column_info = &my_extra->columns[i];
505 Oid column_type = tupdesc->attrs[i]->atttypid;
508 StringInfoData item_buf;
512 /* Ignore dropped columns in datatype, but fill with nulls */
513 if (tupdesc->attrs[i]->attisdropped)
515 values[i] = (Datum) 0;
520 /* Verify column datatype */
521 coltypoid = pq_getmsgint(buf, sizeof(Oid));
522 if (coltypoid != column_type)
524 (errcode(ERRCODE_DATATYPE_MISMATCH),
525 errmsg("wrong data type: %u, expected %u",
526 coltypoid, column_type)));
528 /* Get and check the item length */
529 itemlen = pq_getmsgint(buf, 4);
530 if (itemlen < -1 || itemlen > (buf->len - buf->cursor))
532 (errcode(ERRCODE_INVALID_BINARY_REPRESENTATION),
533 errmsg("insufficient data left in message")));
537 /* -1 length means NULL */
540 csave = 0; /* keep compiler quiet */
545 * Rather than copying data around, we just set up a phony
546 * StringInfo pointing to the correct portion of the input buffer.
547 * We assume we can scribble on the input buffer so as to maintain
548 * the convention that StringInfos have a trailing null.
550 item_buf.data = &buf->data[buf->cursor];
551 item_buf.maxlen = itemlen + 1;
552 item_buf.len = itemlen;
555 buf->cursor += itemlen;
557 csave = buf->data[buf->cursor];
558 buf->data[buf->cursor] = '\0';
564 /* Now call the column's receiveproc */
565 if (column_info->column_type != column_type)
567 getTypeBinaryInputInfo(column_type,
568 &column_info->typiofunc,
569 &column_info->typioparam);
570 fmgr_info_cxt(column_info->typiofunc, &column_info->proc,
571 fcinfo->flinfo->fn_mcxt);
572 column_info->column_type = column_type;
575 values[i] = ReceiveFunctionCall(&column_info->proc,
577 column_info->typioparam,
578 tupdesc->attrs[i]->atttypmod);
582 /* Trouble if it didn't eat the whole buffer */
583 if (item_buf.cursor != itemlen)
585 (errcode(ERRCODE_INVALID_BINARY_REPRESENTATION),
586 errmsg("improper binary format in record column %d",
589 buf->data[buf->cursor] = csave;
593 tuple = heap_formtuple(tupdesc, values, nulls);
596 * We cannot return tuple->t_data because heap_formtuple allocates it as
597 * part of a larger chunk, and our caller may expect to be able to pfree
598 * our result. So must copy the info into a new palloc chunk.
600 result = (HeapTupleHeader) palloc(tuple->t_len);
601 memcpy(result, tuple->t_data, tuple->t_len);
603 heap_freetuple(tuple);
606 ReleaseTupleDesc(tupdesc);
608 PG_RETURN_HEAPTUPLEHEADER(result);
612 * record_send - binary output routine for any composite type.
615 record_send(PG_FUNCTION_ARGS)
617 HeapTupleHeader rec = PG_GETARG_HEAPTUPLEHEADER(0);
622 RecordIOData *my_extra;
630 /* Extract type info from the tuple itself */
631 tupType = HeapTupleHeaderGetTypeId(rec);
632 tupTypmod = HeapTupleHeaderGetTypMod(rec);
633 tupdesc = lookup_rowtype_tupdesc(tupType, tupTypmod);
634 ncolumns = tupdesc->natts;
636 /* Build a temporary HeapTuple control structure */
637 tuple.t_len = HeapTupleHeaderGetDatumLength(rec);
638 ItemPointerSetInvalid(&(tuple.t_self));
639 tuple.t_tableOid = InvalidOid;
643 * We arrange to look up the needed I/O info just once per series of
644 * calls, assuming the record type doesn't change underneath us.
646 my_extra = (RecordIOData *) fcinfo->flinfo->fn_extra;
647 if (my_extra == NULL ||
648 my_extra->ncolumns != ncolumns)
650 fcinfo->flinfo->fn_extra =
651 MemoryContextAlloc(fcinfo->flinfo->fn_mcxt,
652 sizeof(RecordIOData) - sizeof(ColumnIOData)
653 + ncolumns * sizeof(ColumnIOData));
654 my_extra = (RecordIOData *) fcinfo->flinfo->fn_extra;
655 my_extra->record_type = InvalidOid;
656 my_extra->record_typmod = 0;
659 if (my_extra->record_type != tupType ||
660 my_extra->record_typmod != tupTypmod)
663 sizeof(RecordIOData) - sizeof(ColumnIOData)
664 + ncolumns * sizeof(ColumnIOData));
665 my_extra->record_type = tupType;
666 my_extra->record_typmod = tupTypmod;
667 my_extra->ncolumns = ncolumns;
670 values = (Datum *) palloc(ncolumns * sizeof(Datum));
671 nulls = (char *) palloc(ncolumns * sizeof(char));
673 /* Break down the tuple into fields */
674 heap_deformtuple(&tuple, tupdesc, values, nulls);
676 /* And build the result string */
677 pq_begintypsend(&buf);
679 /* Need to scan to count nondeleted columns */
681 for (i = 0; i < ncolumns; i++)
683 if (!tupdesc->attrs[i]->attisdropped)
686 pq_sendint(&buf, validcols, 4);
688 for (i = 0; i < ncolumns; i++)
690 ColumnIOData *column_info = &my_extra->columns[i];
691 Oid column_type = tupdesc->attrs[i]->atttypid;
694 /* Ignore dropped columns in datatype */
695 if (tupdesc->attrs[i]->attisdropped)
698 pq_sendint(&buf, column_type, sizeof(Oid));
702 /* emit -1 data length to signify a NULL */
703 pq_sendint(&buf, -1, 4);
708 * Convert the column value to binary
710 if (column_info->column_type != column_type)
714 getTypeBinaryOutputInfo(column_type,
715 &column_info->typiofunc,
717 fmgr_info_cxt(column_info->typiofunc, &column_info->proc,
718 fcinfo->flinfo->fn_mcxt);
719 column_info->column_type = column_type;
722 outputbytes = SendFunctionCall(&column_info->proc, values[i]);
724 /* We assume the result will not have been toasted */
725 pq_sendint(&buf, VARSIZE(outputbytes) - VARHDRSZ, 4);
726 pq_sendbytes(&buf, VARDATA(outputbytes),
727 VARSIZE(outputbytes) - VARHDRSZ);
733 ReleaseTupleDesc(tupdesc);
735 PG_RETURN_BYTEA_P(pq_endtypsend(&buf));