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.18 2007/01/05 22:19:42 momjian 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;
173 while (inquote || !(*ptr == ',' || *ptr == ')'))
179 (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
180 errmsg("malformed record literal: \"%s\"",
182 errdetail("Unexpected end of input.")));
187 (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
188 errmsg("malformed record literal: \"%s\"",
190 errdetail("Unexpected end of input.")));
191 appendStringInfoChar(&buf, *ptr++);
197 else if (*ptr == '\"')
199 /* doubled quote within quote sequence */
200 appendStringInfoChar(&buf, *ptr++);
206 appendStringInfoChar(&buf, ch);
209 column_data = buf.data;
214 * Convert the column value
216 if (column_info->column_type != column_type)
218 getTypeInputInfo(column_type,
219 &column_info->typiofunc,
220 &column_info->typioparam);
221 fmgr_info_cxt(column_info->typiofunc, &column_info->proc,
222 fcinfo->flinfo->fn_mcxt);
223 column_info->column_type = column_type;
226 values[i] = InputFunctionCall(&column_info->proc,
228 column_info->typioparam,
229 tupdesc->attrs[i]->atttypmod);
232 * Prep for next column
239 (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
240 errmsg("malformed record literal: \"%s\"", string),
241 errdetail("Too many columns.")));
242 /* Allow trailing whitespace */
243 while (*ptr && isspace((unsigned char) *ptr))
247 (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
248 errmsg("malformed record literal: \"%s\"", string),
249 errdetail("Junk after right parenthesis.")));
251 tuple = heap_formtuple(tupdesc, values, nulls);
254 * We cannot return tuple->t_data because heap_formtuple allocates it as
255 * part of a larger chunk, and our caller may expect to be able to pfree
256 * our result. So must copy the info into a new palloc chunk.
258 result = (HeapTupleHeader) palloc(tuple->t_len);
259 memcpy(result, tuple->t_data, tuple->t_len);
261 heap_freetuple(tuple);
265 ReleaseTupleDesc(tupdesc);
267 PG_RETURN_HEAPTUPLEHEADER(result);
271 * record_out - output routine for any composite type.
274 record_out(PG_FUNCTION_ARGS)
276 HeapTupleHeader rec = PG_GETARG_HEAPTUPLEHEADER(0);
281 RecordIOData *my_extra;
282 bool needComma = false;
289 /* Extract type info from the tuple itself */
290 tupType = HeapTupleHeaderGetTypeId(rec);
291 tupTypmod = HeapTupleHeaderGetTypMod(rec);
292 tupdesc = lookup_rowtype_tupdesc(tupType, tupTypmod);
293 ncolumns = tupdesc->natts;
295 /* Build a temporary HeapTuple control structure */
296 tuple.t_len = HeapTupleHeaderGetDatumLength(rec);
297 ItemPointerSetInvalid(&(tuple.t_self));
298 tuple.t_tableOid = InvalidOid;
302 * We arrange to look up the needed I/O info just once per series of
303 * calls, assuming the record type doesn't change underneath us.
305 my_extra = (RecordIOData *) fcinfo->flinfo->fn_extra;
306 if (my_extra == NULL ||
307 my_extra->ncolumns != ncolumns)
309 fcinfo->flinfo->fn_extra =
310 MemoryContextAlloc(fcinfo->flinfo->fn_mcxt,
311 sizeof(RecordIOData) - sizeof(ColumnIOData)
312 + ncolumns * sizeof(ColumnIOData));
313 my_extra = (RecordIOData *) fcinfo->flinfo->fn_extra;
314 my_extra->record_type = InvalidOid;
315 my_extra->record_typmod = 0;
318 if (my_extra->record_type != tupType ||
319 my_extra->record_typmod != tupTypmod)
322 sizeof(RecordIOData) - sizeof(ColumnIOData)
323 + ncolumns * sizeof(ColumnIOData));
324 my_extra->record_type = tupType;
325 my_extra->record_typmod = tupTypmod;
326 my_extra->ncolumns = ncolumns;
329 values = (Datum *) palloc(ncolumns * sizeof(Datum));
330 nulls = (char *) palloc(ncolumns * sizeof(char));
332 /* Break down the tuple into fields */
333 heap_deformtuple(&tuple, tupdesc, values, nulls);
335 /* And build the result string */
336 initStringInfo(&buf);
338 appendStringInfoChar(&buf, '(');
340 for (i = 0; i < ncolumns; i++)
342 ColumnIOData *column_info = &my_extra->columns[i];
343 Oid column_type = tupdesc->attrs[i]->atttypid;
348 /* Ignore dropped columns in datatype */
349 if (tupdesc->attrs[i]->attisdropped)
353 appendStringInfoChar(&buf, ',');
358 /* emit nothing... */
363 * Convert the column value to text
365 if (column_info->column_type != column_type)
369 getTypeOutputInfo(column_type,
370 &column_info->typiofunc,
372 fmgr_info_cxt(column_info->typiofunc, &column_info->proc,
373 fcinfo->flinfo->fn_mcxt);
374 column_info->column_type = column_type;
377 value = OutputFunctionCall(&column_info->proc, values[i]);
379 /* Detect whether we need double quotes for this value */
380 nq = (value[0] == '\0'); /* force quotes for empty string */
381 for (tmp = value; *tmp; tmp++)
385 if (ch == '"' || ch == '\\' ||
386 ch == '(' || ch == ')' || ch == ',' ||
387 isspace((unsigned char) ch))
394 /* And emit the string */
396 appendStringInfoChar(&buf, '"');
397 for (tmp = value; *tmp; tmp++)
401 if (ch == '"' || ch == '\\')
402 appendStringInfoChar(&buf, ch);
403 appendStringInfoChar(&buf, ch);
406 appendStringInfoChar(&buf, '"');
409 appendStringInfoChar(&buf, ')');
413 ReleaseTupleDesc(tupdesc);
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);
608 ReleaseTupleDesc(tupdesc);
610 PG_RETURN_HEAPTUPLEHEADER(result);
614 * record_send - binary output routine for any composite type.
617 record_send(PG_FUNCTION_ARGS)
619 HeapTupleHeader rec = PG_GETARG_HEAPTUPLEHEADER(0);
624 RecordIOData *my_extra;
632 /* Extract type info from the tuple itself */
633 tupType = HeapTupleHeaderGetTypeId(rec);
634 tupTypmod = HeapTupleHeaderGetTypMod(rec);
635 tupdesc = lookup_rowtype_tupdesc(tupType, tupTypmod);
636 ncolumns = tupdesc->natts;
638 /* Build a temporary HeapTuple control structure */
639 tuple.t_len = HeapTupleHeaderGetDatumLength(rec);
640 ItemPointerSetInvalid(&(tuple.t_self));
641 tuple.t_tableOid = InvalidOid;
645 * We arrange to look up the needed I/O info just once per series of
646 * calls, assuming the record type doesn't change underneath us.
648 my_extra = (RecordIOData *) fcinfo->flinfo->fn_extra;
649 if (my_extra == NULL ||
650 my_extra->ncolumns != ncolumns)
652 fcinfo->flinfo->fn_extra =
653 MemoryContextAlloc(fcinfo->flinfo->fn_mcxt,
654 sizeof(RecordIOData) - sizeof(ColumnIOData)
655 + ncolumns * sizeof(ColumnIOData));
656 my_extra = (RecordIOData *) fcinfo->flinfo->fn_extra;
657 my_extra->record_type = InvalidOid;
658 my_extra->record_typmod = 0;
661 if (my_extra->record_type != tupType ||
662 my_extra->record_typmod != tupTypmod)
665 sizeof(RecordIOData) - sizeof(ColumnIOData)
666 + ncolumns * sizeof(ColumnIOData));
667 my_extra->record_type = tupType;
668 my_extra->record_typmod = tupTypmod;
669 my_extra->ncolumns = ncolumns;
672 values = (Datum *) palloc(ncolumns * sizeof(Datum));
673 nulls = (char *) palloc(ncolumns * sizeof(char));
675 /* Break down the tuple into fields */
676 heap_deformtuple(&tuple, tupdesc, values, nulls);
678 /* And build the result string */
679 pq_begintypsend(&buf);
681 /* Need to scan to count nondeleted columns */
683 for (i = 0; i < ncolumns; i++)
685 if (!tupdesc->attrs[i]->attisdropped)
688 pq_sendint(&buf, validcols, 4);
690 for (i = 0; i < ncolumns; i++)
692 ColumnIOData *column_info = &my_extra->columns[i];
693 Oid column_type = tupdesc->attrs[i]->atttypid;
696 /* Ignore dropped columns in datatype */
697 if (tupdesc->attrs[i]->attisdropped)
700 pq_sendint(&buf, column_type, sizeof(Oid));
704 /* emit -1 data length to signify a NULL */
705 pq_sendint(&buf, -1, 4);
710 * Convert the column value to binary
712 if (column_info->column_type != column_type)
716 getTypeBinaryOutputInfo(column_type,
717 &column_info->typiofunc,
719 fmgr_info_cxt(column_info->typiofunc, &column_info->proc,
720 fcinfo->flinfo->fn_mcxt);
721 column_info->column_type = column_type;
724 outputbytes = SendFunctionCall(&column_info->proc, values[i]);
726 /* We assume the result will not have been toasted */
727 pq_sendint(&buf, VARSIZE(outputbytes) - VARHDRSZ, 4);
728 pq_sendbytes(&buf, VARDATA(outputbytes),
729 VARSIZE(outputbytes) - VARHDRSZ);
735 ReleaseTupleDesc(tupdesc);
737 PG_RETURN_BYTEA_P(pq_endtypsend(&buf));