1 /*-------------------------------------------------------------------------
4 * I/O functions for generic composite types.
6 * Portions Copyright (c) 1996-2005, 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.9 2005/04/18 17:11:05 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);
57 HeapTupleHeader result;
61 RecordIOData *my_extra;
62 bool needComma = false;
71 * Use the passed type unless it's RECORD; we can't support input of
72 * anonymous types, mainly because there's no good way to figure out
73 * which anonymous type is wanted. Note that for RECORD, what we'll
74 * probably actually get is RECORD's typelem, ie, zero.
76 if (tupType == InvalidOid || tupType == RECORDOID)
78 (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
79 errmsg("input of anonymous composite types is not implemented")));
80 tupTypmod = -1; /* for all non-anonymous types */
81 tupdesc = lookup_rowtype_tupdesc(tupType, tupTypmod);
82 ncolumns = tupdesc->natts;
85 * We arrange to look up the needed I/O info just once per series of
86 * calls, assuming the record type doesn't change underneath us.
88 my_extra = (RecordIOData *) fcinfo->flinfo->fn_extra;
89 if (my_extra == NULL ||
90 my_extra->ncolumns != ncolumns)
92 fcinfo->flinfo->fn_extra =
93 MemoryContextAlloc(fcinfo->flinfo->fn_mcxt,
94 sizeof(RecordIOData) - sizeof(ColumnIOData)
95 + ncolumns * sizeof(ColumnIOData));
96 my_extra = (RecordIOData *) fcinfo->flinfo->fn_extra;
97 my_extra->record_type = InvalidOid;
98 my_extra->record_typmod = 0;
101 if (my_extra->record_type != tupType ||
102 my_extra->record_typmod != tupTypmod)
105 sizeof(RecordIOData) - sizeof(ColumnIOData)
106 + ncolumns * sizeof(ColumnIOData));
107 my_extra->record_type = tupType;
108 my_extra->record_typmod = tupTypmod;
109 my_extra->ncolumns = ncolumns;
112 values = (Datum *) palloc(ncolumns * sizeof(Datum));
113 nulls = (char *) palloc(ncolumns * sizeof(char));
116 * Scan the string. We use "buf" to accumulate the de-quoted data for
117 * each column, which is then fed to the appropriate input converter.
120 /* Allow leading whitespace */
121 while (*ptr && isspace((unsigned char) *ptr))
125 (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
126 errmsg("malformed record literal: \"%s\"", string),
127 errdetail("Missing left parenthesis.")));
129 initStringInfo(&buf);
131 for (i = 0; i < ncolumns; i++)
133 ColumnIOData *column_info = &my_extra->columns[i];
134 Oid column_type = tupdesc->attrs[i]->atttypid;
136 /* Ignore dropped columns in datatype, but fill with nulls */
137 if (tupdesc->attrs[i]->attisdropped)
139 values[i] = (Datum) 0;
146 /* Skip comma that separates prior field from this one */
150 /* *ptr must be ')' */
152 (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
153 errmsg("malformed record literal: \"%s\"", string),
154 errdetail("Too few columns.")));
157 /* Check for null: completely empty input means null */
158 if (*ptr == ',' || *ptr == ')')
160 values[i] = (Datum) 0;
165 /* Extract string for this column */
166 bool inquote = false;
170 while (inquote || !(*ptr == ',' || *ptr == ')'))
176 (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
177 errmsg("malformed record literal: \"%s\"",
179 errdetail("Unexpected end of input.")));
184 (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
185 errmsg("malformed record literal: \"%s\"",
187 errdetail("Unexpected end of input.")));
188 appendStringInfoChar(&buf, *ptr++);
194 else if (*ptr == '\"')
196 /* doubled quote within quote sequence */
197 appendStringInfoChar(&buf, *ptr++);
203 appendStringInfoChar(&buf, ch);
207 * Convert the column value
209 if (column_info->column_type != column_type)
211 getTypeInputInfo(column_type,
212 &column_info->typiofunc,
213 &column_info->typioparam);
214 fmgr_info_cxt(column_info->typiofunc, &column_info->proc,
215 fcinfo->flinfo->fn_mcxt);
216 column_info->column_type = column_type;
219 values[i] = FunctionCall3(&column_info->proc,
220 CStringGetDatum(buf.data),
221 ObjectIdGetDatum(column_info->typioparam),
222 Int32GetDatum(tupdesc->attrs[i]->atttypmod));
227 * Prep for next column
234 (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
235 errmsg("malformed record literal: \"%s\"", string),
236 errdetail("Too many columns.")));
237 /* Allow trailing whitespace */
238 while (*ptr && isspace((unsigned char) *ptr))
242 (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
243 errmsg("malformed record literal: \"%s\"", string),
244 errdetail("Junk after right parenthesis.")));
246 tuple = heap_formtuple(tupdesc, values, nulls);
249 * We cannot return tuple->t_data because heap_formtuple allocates it
250 * as part of a larger chunk, and our caller may expect to be able to
251 * pfree our result. So must copy the info into a new palloc chunk.
253 result = (HeapTupleHeader) palloc(tuple->t_len);
254 memcpy(result, tuple->t_data, tuple->t_len);
256 heap_freetuple(tuple);
261 PG_RETURN_HEAPTUPLEHEADER(result);
265 * record_out - output routine for any composite type.
268 record_out(PG_FUNCTION_ARGS)
270 HeapTupleHeader rec = PG_GETARG_HEAPTUPLEHEADER(0);
271 Oid tupType = PG_GETARG_OID(1);
275 RecordIOData *my_extra;
276 bool needComma = false;
284 * Use the passed type unless it's RECORD; in that case, we'd better
285 * get the type info out of the datum itself. Note that for RECORD,
286 * what we'll probably actually get is RECORD's typelem, ie, zero.
288 if (tupType == InvalidOid || tupType == RECORDOID)
290 tupType = HeapTupleHeaderGetTypeId(rec);
291 tupTypmod = HeapTupleHeaderGetTypMod(rec);
295 tupdesc = lookup_rowtype_tupdesc(tupType, tupTypmod);
296 ncolumns = tupdesc->natts;
298 /* Build a temporary HeapTuple control structure */
299 tuple.t_len = HeapTupleHeaderGetDatumLength(rec);
300 ItemPointerSetInvalid(&(tuple.t_self));
301 tuple.t_tableOid = InvalidOid;
305 * We arrange to look up the needed I/O info just once per series of
306 * calls, assuming the record type doesn't change underneath us.
308 my_extra = (RecordIOData *) fcinfo->flinfo->fn_extra;
309 if (my_extra == NULL ||
310 my_extra->ncolumns != ncolumns)
312 fcinfo->flinfo->fn_extra =
313 MemoryContextAlloc(fcinfo->flinfo->fn_mcxt,
314 sizeof(RecordIOData) - sizeof(ColumnIOData)
315 + ncolumns * sizeof(ColumnIOData));
316 my_extra = (RecordIOData *) fcinfo->flinfo->fn_extra;
317 my_extra->record_type = InvalidOid;
318 my_extra->record_typmod = 0;
321 if (my_extra->record_type != tupType ||
322 my_extra->record_typmod != tupTypmod)
325 sizeof(RecordIOData) - sizeof(ColumnIOData)
326 + ncolumns * sizeof(ColumnIOData));
327 my_extra->record_type = tupType;
328 my_extra->record_typmod = tupTypmod;
329 my_extra->ncolumns = ncolumns;
332 values = (Datum *) palloc(ncolumns * sizeof(Datum));
333 nulls = (char *) palloc(ncolumns * sizeof(char));
335 /* Break down the tuple into fields */
336 heap_deformtuple(&tuple, tupdesc, values, nulls);
338 /* And build the result string */
339 initStringInfo(&buf);
341 appendStringInfoChar(&buf, '(');
343 for (i = 0; i < ncolumns; i++)
345 ColumnIOData *column_info = &my_extra->columns[i];
346 Oid column_type = tupdesc->attrs[i]->atttypid;
351 /* Ignore dropped columns in datatype */
352 if (tupdesc->attrs[i]->attisdropped)
356 appendStringInfoChar(&buf, ',');
361 /* emit nothing... */
366 * Convert the column value to text
368 if (column_info->column_type != column_type)
372 getTypeOutputInfo(column_type,
373 &column_info->typiofunc,
374 &column_info->typioparam,
376 fmgr_info_cxt(column_info->typiofunc, &column_info->proc,
377 fcinfo->flinfo->fn_mcxt);
378 column_info->column_type = column_type;
381 value = DatumGetCString(FunctionCall3(&column_info->proc,
383 ObjectIdGetDatum(column_info->typioparam),
384 Int32GetDatum(tupdesc->attrs[i]->atttypmod)));
386 /* Detect whether we need double quotes for this value */
387 nq = (value[0] == '\0'); /* force quotes for empty string */
388 for (tmp = value; *tmp; tmp++)
392 if (ch == '"' || ch == '\\' ||
393 ch == '(' || ch == ')' || ch == ',' ||
394 isspace((unsigned char) ch))
401 /* And emit the string */
403 appendStringInfoChar(&buf, '"');
404 for (tmp = value; *tmp; tmp++)
408 if (ch == '"' || ch == '\\')
409 appendStringInfoChar(&buf, ch);
410 appendStringInfoChar(&buf, ch);
413 appendStringInfoChar(&buf, '"');
416 appendStringInfoChar(&buf, ')');
421 PG_RETURN_CSTRING(buf.data);
425 * record_recv - binary input routine for any composite type.
428 record_recv(PG_FUNCTION_ARGS)
430 StringInfo buf = (StringInfo) PG_GETARG_POINTER(0);
431 Oid tupType = PG_GETARG_OID(1);
432 HeapTupleHeader result;
436 RecordIOData *my_extra;
445 * Use the passed type unless it's RECORD; we can't support input of
446 * anonymous types, mainly because there's no good way to figure out
447 * which anonymous type is wanted. Note that for RECORD, what we'll
448 * probably actually get is RECORD's typelem, ie, zero.
450 if (tupType == InvalidOid || tupType == RECORDOID)
452 (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
453 errmsg("input of anonymous composite types is not implemented")));
454 tupTypmod = -1; /* for all non-anonymous types */
455 tupdesc = lookup_rowtype_tupdesc(tupType, tupTypmod);
456 ncolumns = tupdesc->natts;
459 * We arrange to look up the needed I/O info just once per series of
460 * calls, assuming the record type doesn't change underneath us.
462 my_extra = (RecordIOData *) fcinfo->flinfo->fn_extra;
463 if (my_extra == NULL ||
464 my_extra->ncolumns != ncolumns)
466 fcinfo->flinfo->fn_extra =
467 MemoryContextAlloc(fcinfo->flinfo->fn_mcxt,
468 sizeof(RecordIOData) - sizeof(ColumnIOData)
469 + ncolumns * sizeof(ColumnIOData));
470 my_extra = (RecordIOData *) fcinfo->flinfo->fn_extra;
471 my_extra->record_type = InvalidOid;
472 my_extra->record_typmod = 0;
475 if (my_extra->record_type != tupType ||
476 my_extra->record_typmod != tupTypmod)
479 sizeof(RecordIOData) - sizeof(ColumnIOData)
480 + ncolumns * sizeof(ColumnIOData));
481 my_extra->record_type = tupType;
482 my_extra->record_typmod = tupTypmod;
483 my_extra->ncolumns = ncolumns;
486 values = (Datum *) palloc(ncolumns * sizeof(Datum));
487 nulls = (char *) palloc(ncolumns * sizeof(char));
489 /* Fetch number of columns user thinks it has */
490 usercols = pq_getmsgint(buf, 4);
492 /* Need to scan to count nondeleted columns */
494 for (i = 0; i < ncolumns; i++)
496 if (!tupdesc->attrs[i]->attisdropped)
499 if (usercols != validcols)
501 (errcode(ERRCODE_DATATYPE_MISMATCH),
502 errmsg("wrong number of columns: %d, expected %d",
503 usercols, validcols)));
505 /* Process each column */
506 for (i = 0; i < ncolumns; i++)
508 ColumnIOData *column_info = &my_extra->columns[i];
509 Oid column_type = tupdesc->attrs[i]->atttypid;
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 */
539 values[i] = (Datum) 0;
545 * Rather than copying data around, we just set up a phony
546 * StringInfo pointing to the correct portion of the input
547 * buffer. We assume we can scribble on the input buffer so as
548 * to maintain the convention that StringInfos have a trailing
551 StringInfoData item_buf;
554 item_buf.data = &buf->data[buf->cursor];
555 item_buf.maxlen = itemlen + 1;
556 item_buf.len = itemlen;
559 buf->cursor += itemlen;
561 csave = buf->data[buf->cursor];
562 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] = FunctionCall2(&column_info->proc,
576 PointerGetDatum(&item_buf),
577 ObjectIdGetDatum(column_info->typioparam));
581 /* Trouble if it didn't eat the whole buffer */
582 if (item_buf.cursor != itemlen)
584 (errcode(ERRCODE_INVALID_BINARY_REPRESENTATION),
585 errmsg("improper binary format in record column %d",
588 buf->data[buf->cursor] = csave;
592 tuple = heap_formtuple(tupdesc, values, nulls);
595 * We cannot return tuple->t_data because heap_formtuple allocates it
596 * as part of a larger chunk, and our caller may expect to be able to
597 * pfree our result. So must copy the info into a new palloc chunk.
599 result = (HeapTupleHeader) palloc(tuple->t_len);
600 memcpy(result, tuple->t_data, tuple->t_len);
602 heap_freetuple(tuple);
606 PG_RETURN_HEAPTUPLEHEADER(result);
610 * record_send - binary output routine for any composite type.
613 record_send(PG_FUNCTION_ARGS)
615 HeapTupleHeader rec = PG_GETARG_HEAPTUPLEHEADER(0);
616 Oid tupType = PG_GETARG_OID(1);
620 RecordIOData *my_extra;
629 * Use the passed type unless it's RECORD; in that case, we'd better
630 * get the type info out of the datum itself. Note that for RECORD,
631 * what we'll probably actually get is RECORD's typelem, ie, zero.
633 if (tupType == InvalidOid || tupType == RECORDOID)
635 tupType = HeapTupleHeaderGetTypeId(rec);
636 tupTypmod = HeapTupleHeaderGetTypMod(rec);
640 tupdesc = lookup_rowtype_tupdesc(tupType, tupTypmod);
641 ncolumns = tupdesc->natts;
643 /* Build a temporary HeapTuple control structure */
644 tuple.t_len = HeapTupleHeaderGetDatumLength(rec);
645 ItemPointerSetInvalid(&(tuple.t_self));
646 tuple.t_tableOid = InvalidOid;
650 * We arrange to look up the needed I/O info just once per series of
651 * calls, assuming the record type doesn't change underneath us.
653 my_extra = (RecordIOData *) fcinfo->flinfo->fn_extra;
654 if (my_extra == NULL ||
655 my_extra->ncolumns != ncolumns)
657 fcinfo->flinfo->fn_extra =
658 MemoryContextAlloc(fcinfo->flinfo->fn_mcxt,
659 sizeof(RecordIOData) - sizeof(ColumnIOData)
660 + ncolumns * sizeof(ColumnIOData));
661 my_extra = (RecordIOData *) fcinfo->flinfo->fn_extra;
662 my_extra->record_type = InvalidOid;
663 my_extra->record_typmod = 0;
666 if (my_extra->record_type != tupType ||
667 my_extra->record_typmod != tupTypmod)
670 sizeof(RecordIOData) - sizeof(ColumnIOData)
671 + ncolumns * sizeof(ColumnIOData));
672 my_extra->record_type = tupType;
673 my_extra->record_typmod = tupTypmod;
674 my_extra->ncolumns = ncolumns;
677 values = (Datum *) palloc(ncolumns * sizeof(Datum));
678 nulls = (char *) palloc(ncolumns * sizeof(char));
680 /* Break down the tuple into fields */
681 heap_deformtuple(&tuple, tupdesc, values, nulls);
683 /* And build the result string */
684 pq_begintypsend(&buf);
686 /* Need to scan to count nondeleted columns */
688 for (i = 0; i < ncolumns; i++)
690 if (!tupdesc->attrs[i]->attisdropped)
693 pq_sendint(&buf, validcols, 4);
695 for (i = 0; i < ncolumns; i++)
697 ColumnIOData *column_info = &my_extra->columns[i];
698 Oid column_type = tupdesc->attrs[i]->atttypid;
701 /* Ignore dropped columns in datatype */
702 if (tupdesc->attrs[i]->attisdropped)
705 pq_sendint(&buf, column_type, sizeof(Oid));
709 /* emit -1 data length to signify a NULL */
710 pq_sendint(&buf, -1, 4);
715 * Convert the column value to binary
717 if (column_info->column_type != column_type)
721 getTypeBinaryOutputInfo(column_type,
722 &column_info->typiofunc,
723 &column_info->typioparam,
725 fmgr_info_cxt(column_info->typiofunc, &column_info->proc,
726 fcinfo->flinfo->fn_mcxt);
727 column_info->column_type = column_type;
730 outputbytes = DatumGetByteaP(FunctionCall2(&column_info->proc,
732 ObjectIdGetDatum(column_info->typioparam)));
734 /* We assume the result will not have been toasted */
735 pq_sendint(&buf, VARSIZE(outputbytes) - VARHDRSZ, 4);
736 pq_sendbytes(&buf, VARDATA(outputbytes),
737 VARSIZE(outputbytes) - VARHDRSZ);
744 PG_RETURN_BYTEA_P(pq_endtypsend(&buf));