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.11 2005/05/01 18:56:18 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);
275 RecordIOData *my_extra;
276 bool needComma = false;
283 /* Extract type info from the tuple itself */
284 tupType = HeapTupleHeaderGetTypeId(rec);
285 tupTypmod = HeapTupleHeaderGetTypMod(rec);
286 tupdesc = lookup_rowtype_tupdesc(tupType, tupTypmod);
287 ncolumns = tupdesc->natts;
289 /* Build a temporary HeapTuple control structure */
290 tuple.t_len = HeapTupleHeaderGetDatumLength(rec);
291 ItemPointerSetInvalid(&(tuple.t_self));
292 tuple.t_tableOid = InvalidOid;
296 * We arrange to look up the needed I/O info just once per series of
297 * calls, assuming the record type doesn't change underneath us.
299 my_extra = (RecordIOData *) fcinfo->flinfo->fn_extra;
300 if (my_extra == NULL ||
301 my_extra->ncolumns != ncolumns)
303 fcinfo->flinfo->fn_extra =
304 MemoryContextAlloc(fcinfo->flinfo->fn_mcxt,
305 sizeof(RecordIOData) - sizeof(ColumnIOData)
306 + ncolumns * sizeof(ColumnIOData));
307 my_extra = (RecordIOData *) fcinfo->flinfo->fn_extra;
308 my_extra->record_type = InvalidOid;
309 my_extra->record_typmod = 0;
312 if (my_extra->record_type != tupType ||
313 my_extra->record_typmod != tupTypmod)
316 sizeof(RecordIOData) - sizeof(ColumnIOData)
317 + ncolumns * sizeof(ColumnIOData));
318 my_extra->record_type = tupType;
319 my_extra->record_typmod = tupTypmod;
320 my_extra->ncolumns = ncolumns;
323 values = (Datum *) palloc(ncolumns * sizeof(Datum));
324 nulls = (char *) palloc(ncolumns * sizeof(char));
326 /* Break down the tuple into fields */
327 heap_deformtuple(&tuple, tupdesc, values, nulls);
329 /* And build the result string */
330 initStringInfo(&buf);
332 appendStringInfoChar(&buf, '(');
334 for (i = 0; i < ncolumns; i++)
336 ColumnIOData *column_info = &my_extra->columns[i];
337 Oid column_type = tupdesc->attrs[i]->atttypid;
342 /* Ignore dropped columns in datatype */
343 if (tupdesc->attrs[i]->attisdropped)
347 appendStringInfoChar(&buf, ',');
352 /* emit nothing... */
357 * Convert the column value to text
359 if (column_info->column_type != column_type)
363 getTypeOutputInfo(column_type,
364 &column_info->typiofunc,
366 fmgr_info_cxt(column_info->typiofunc, &column_info->proc,
367 fcinfo->flinfo->fn_mcxt);
368 column_info->column_type = column_type;
371 value = DatumGetCString(FunctionCall1(&column_info->proc,
374 /* Detect whether we need double quotes for this value */
375 nq = (value[0] == '\0'); /* force quotes for empty string */
376 for (tmp = value; *tmp; tmp++)
380 if (ch == '"' || ch == '\\' ||
381 ch == '(' || ch == ')' || ch == ',' ||
382 isspace((unsigned char) ch))
389 /* And emit the string */
391 appendStringInfoChar(&buf, '"');
392 for (tmp = value; *tmp; tmp++)
396 if (ch == '"' || ch == '\\')
397 appendStringInfoChar(&buf, ch);
398 appendStringInfoChar(&buf, ch);
401 appendStringInfoChar(&buf, '"');
404 appendStringInfoChar(&buf, ')');
409 PG_RETURN_CSTRING(buf.data);
413 * record_recv - binary input routine for any composite type.
416 record_recv(PG_FUNCTION_ARGS)
418 StringInfo buf = (StringInfo) PG_GETARG_POINTER(0);
419 Oid tupType = PG_GETARG_OID(1);
420 HeapTupleHeader result;
424 RecordIOData *my_extra;
433 * Use the passed type unless it's RECORD; we can't support input of
434 * anonymous types, mainly because there's no good way to figure out
435 * which anonymous type is wanted. Note that for RECORD, what we'll
436 * probably actually get is RECORD's typelem, ie, zero.
438 if (tupType == InvalidOid || tupType == RECORDOID)
440 (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
441 errmsg("input of anonymous composite types is not implemented")));
442 tupTypmod = -1; /* for all non-anonymous types */
443 tupdesc = lookup_rowtype_tupdesc(tupType, tupTypmod);
444 ncolumns = tupdesc->natts;
447 * We arrange to look up the needed I/O info just once per series of
448 * calls, assuming the record type doesn't change underneath us.
450 my_extra = (RecordIOData *) fcinfo->flinfo->fn_extra;
451 if (my_extra == NULL ||
452 my_extra->ncolumns != ncolumns)
454 fcinfo->flinfo->fn_extra =
455 MemoryContextAlloc(fcinfo->flinfo->fn_mcxt,
456 sizeof(RecordIOData) - sizeof(ColumnIOData)
457 + ncolumns * sizeof(ColumnIOData));
458 my_extra = (RecordIOData *) fcinfo->flinfo->fn_extra;
459 my_extra->record_type = InvalidOid;
460 my_extra->record_typmod = 0;
463 if (my_extra->record_type != tupType ||
464 my_extra->record_typmod != tupTypmod)
467 sizeof(RecordIOData) - sizeof(ColumnIOData)
468 + ncolumns * sizeof(ColumnIOData));
469 my_extra->record_type = tupType;
470 my_extra->record_typmod = tupTypmod;
471 my_extra->ncolumns = ncolumns;
474 values = (Datum *) palloc(ncolumns * sizeof(Datum));
475 nulls = (char *) palloc(ncolumns * sizeof(char));
477 /* Fetch number of columns user thinks it has */
478 usercols = pq_getmsgint(buf, 4);
480 /* Need to scan to count nondeleted columns */
482 for (i = 0; i < ncolumns; i++)
484 if (!tupdesc->attrs[i]->attisdropped)
487 if (usercols != validcols)
489 (errcode(ERRCODE_DATATYPE_MISMATCH),
490 errmsg("wrong number of columns: %d, expected %d",
491 usercols, validcols)));
493 /* Process each column */
494 for (i = 0; i < ncolumns; i++)
496 ColumnIOData *column_info = &my_extra->columns[i];
497 Oid column_type = tupdesc->attrs[i]->atttypid;
501 /* Ignore dropped columns in datatype, but fill with nulls */
502 if (tupdesc->attrs[i]->attisdropped)
504 values[i] = (Datum) 0;
509 /* Verify column datatype */
510 coltypoid = pq_getmsgint(buf, sizeof(Oid));
511 if (coltypoid != column_type)
513 (errcode(ERRCODE_DATATYPE_MISMATCH),
514 errmsg("wrong data type: %u, expected %u",
515 coltypoid, column_type)));
517 /* Get and check the item length */
518 itemlen = pq_getmsgint(buf, 4);
519 if (itemlen < -1 || itemlen > (buf->len - buf->cursor))
521 (errcode(ERRCODE_INVALID_BINARY_REPRESENTATION),
522 errmsg("insufficient data left in message")));
526 /* -1 length means NULL */
527 values[i] = (Datum) 0;
533 * Rather than copying data around, we just set up a phony
534 * StringInfo pointing to the correct portion of the input
535 * buffer. We assume we can scribble on the input buffer so as
536 * to maintain the convention that StringInfos have a trailing
539 StringInfoData item_buf;
542 item_buf.data = &buf->data[buf->cursor];
543 item_buf.maxlen = itemlen + 1;
544 item_buf.len = itemlen;
547 buf->cursor += itemlen;
549 csave = buf->data[buf->cursor];
550 buf->data[buf->cursor] = '\0';
552 /* Now call the column's receiveproc */
553 if (column_info->column_type != column_type)
555 getTypeBinaryInputInfo(column_type,
556 &column_info->typiofunc,
557 &column_info->typioparam);
558 fmgr_info_cxt(column_info->typiofunc, &column_info->proc,
559 fcinfo->flinfo->fn_mcxt);
560 column_info->column_type = column_type;
563 values[i] = FunctionCall2(&column_info->proc,
564 PointerGetDatum(&item_buf),
565 ObjectIdGetDatum(column_info->typioparam));
569 /* Trouble if it didn't eat the whole buffer */
570 if (item_buf.cursor != itemlen)
572 (errcode(ERRCODE_INVALID_BINARY_REPRESENTATION),
573 errmsg("improper binary format in record column %d",
576 buf->data[buf->cursor] = csave;
580 tuple = heap_formtuple(tupdesc, values, nulls);
583 * We cannot return tuple->t_data because heap_formtuple allocates it
584 * as part of a larger chunk, and our caller may expect to be able to
585 * pfree our result. So must copy the info into a new palloc chunk.
587 result = (HeapTupleHeader) palloc(tuple->t_len);
588 memcpy(result, tuple->t_data, tuple->t_len);
590 heap_freetuple(tuple);
594 PG_RETURN_HEAPTUPLEHEADER(result);
598 * record_send - binary output routine for any composite type.
601 record_send(PG_FUNCTION_ARGS)
603 HeapTupleHeader rec = PG_GETARG_HEAPTUPLEHEADER(0);
608 RecordIOData *my_extra;
616 /* Extract type info from the tuple itself */
617 tupType = HeapTupleHeaderGetTypeId(rec);
618 tupTypmod = HeapTupleHeaderGetTypMod(rec);
619 tupdesc = lookup_rowtype_tupdesc(tupType, tupTypmod);
620 ncolumns = tupdesc->natts;
622 /* Build a temporary HeapTuple control structure */
623 tuple.t_len = HeapTupleHeaderGetDatumLength(rec);
624 ItemPointerSetInvalid(&(tuple.t_self));
625 tuple.t_tableOid = InvalidOid;
629 * We arrange to look up the needed I/O info just once per series of
630 * calls, assuming the record type doesn't change underneath us.
632 my_extra = (RecordIOData *) fcinfo->flinfo->fn_extra;
633 if (my_extra == NULL ||
634 my_extra->ncolumns != ncolumns)
636 fcinfo->flinfo->fn_extra =
637 MemoryContextAlloc(fcinfo->flinfo->fn_mcxt,
638 sizeof(RecordIOData) - sizeof(ColumnIOData)
639 + ncolumns * sizeof(ColumnIOData));
640 my_extra = (RecordIOData *) fcinfo->flinfo->fn_extra;
641 my_extra->record_type = InvalidOid;
642 my_extra->record_typmod = 0;
645 if (my_extra->record_type != tupType ||
646 my_extra->record_typmod != tupTypmod)
649 sizeof(RecordIOData) - sizeof(ColumnIOData)
650 + ncolumns * sizeof(ColumnIOData));
651 my_extra->record_type = tupType;
652 my_extra->record_typmod = tupTypmod;
653 my_extra->ncolumns = ncolumns;
656 values = (Datum *) palloc(ncolumns * sizeof(Datum));
657 nulls = (char *) palloc(ncolumns * sizeof(char));
659 /* Break down the tuple into fields */
660 heap_deformtuple(&tuple, tupdesc, values, nulls);
662 /* And build the result string */
663 pq_begintypsend(&buf);
665 /* Need to scan to count nondeleted columns */
667 for (i = 0; i < ncolumns; i++)
669 if (!tupdesc->attrs[i]->attisdropped)
672 pq_sendint(&buf, validcols, 4);
674 for (i = 0; i < ncolumns; i++)
676 ColumnIOData *column_info = &my_extra->columns[i];
677 Oid column_type = tupdesc->attrs[i]->atttypid;
680 /* Ignore dropped columns in datatype */
681 if (tupdesc->attrs[i]->attisdropped)
684 pq_sendint(&buf, column_type, sizeof(Oid));
688 /* emit -1 data length to signify a NULL */
689 pq_sendint(&buf, -1, 4);
694 * Convert the column value to binary
696 if (column_info->column_type != column_type)
700 getTypeBinaryOutputInfo(column_type,
701 &column_info->typiofunc,
703 fmgr_info_cxt(column_info->typiofunc, &column_info->proc,
704 fcinfo->flinfo->fn_mcxt);
705 column_info->column_type = column_type;
708 outputbytes = DatumGetByteaP(FunctionCall1(&column_info->proc,
711 /* We assume the result will not have been toasted */
712 pq_sendint(&buf, VARSIZE(outputbytes) - VARHDRSZ, 4);
713 pq_sendbytes(&buf, VARDATA(outputbytes),
714 VARSIZE(outputbytes) - VARHDRSZ);
721 PG_RETURN_BYTEA_P(pq_endtypsend(&buf));