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.12 2005/07/10 21:13:59 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);
58 int32 typmod = PG_GETARG_INT32(2);
60 HeapTupleHeader result;
64 RecordIOData *my_extra;
65 bool needComma = false;
74 * Use the passed type unless it's RECORD; we can't support input of
75 * anonymous types, mainly because there's no good way to figure out
76 * which anonymous type is wanted. Note that for RECORD, what we'll
77 * probably actually get is RECORD's typelem, ie, zero.
79 if (tupType == InvalidOid || tupType == RECORDOID)
81 (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
82 errmsg("input of anonymous composite types is not implemented")));
83 tupTypmod = -1; /* for all non-anonymous types */
84 tupdesc = lookup_rowtype_tupdesc(tupType, tupTypmod);
85 ncolumns = tupdesc->natts;
88 * We arrange to look up the needed I/O info just once per series of
89 * calls, assuming the record type doesn't change underneath us.
91 my_extra = (RecordIOData *) fcinfo->flinfo->fn_extra;
92 if (my_extra == NULL ||
93 my_extra->ncolumns != ncolumns)
95 fcinfo->flinfo->fn_extra =
96 MemoryContextAlloc(fcinfo->flinfo->fn_mcxt,
97 sizeof(RecordIOData) - sizeof(ColumnIOData)
98 + ncolumns * sizeof(ColumnIOData));
99 my_extra = (RecordIOData *) fcinfo->flinfo->fn_extra;
100 my_extra->record_type = InvalidOid;
101 my_extra->record_typmod = 0;
104 if (my_extra->record_type != tupType ||
105 my_extra->record_typmod != tupTypmod)
108 sizeof(RecordIOData) - sizeof(ColumnIOData)
109 + ncolumns * sizeof(ColumnIOData));
110 my_extra->record_type = tupType;
111 my_extra->record_typmod = tupTypmod;
112 my_extra->ncolumns = ncolumns;
115 values = (Datum *) palloc(ncolumns * sizeof(Datum));
116 nulls = (char *) palloc(ncolumns * sizeof(char));
119 * Scan the string. We use "buf" to accumulate the de-quoted data for
120 * each column, which is then fed to the appropriate input converter.
123 /* Allow leading whitespace */
124 while (*ptr && isspace((unsigned char) *ptr))
128 (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
129 errmsg("malformed record literal: \"%s\"", string),
130 errdetail("Missing left parenthesis.")));
132 initStringInfo(&buf);
134 for (i = 0; i < ncolumns; i++)
136 ColumnIOData *column_info = &my_extra->columns[i];
137 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 == ')')
163 values[i] = (Datum) 0;
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);
210 * Convert the column value
212 if (column_info->column_type != column_type)
214 getTypeInputInfo(column_type,
215 &column_info->typiofunc,
216 &column_info->typioparam);
217 fmgr_info_cxt(column_info->typiofunc, &column_info->proc,
218 fcinfo->flinfo->fn_mcxt);
219 column_info->column_type = column_type;
222 values[i] = FunctionCall3(&column_info->proc,
223 CStringGetDatum(buf.data),
224 ObjectIdGetDatum(column_info->typioparam),
225 Int32GetDatum(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
253 * as part of a larger chunk, and our caller may expect to be able to
254 * pfree 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);
264 PG_RETURN_HEAPTUPLEHEADER(result);
268 * record_out - output routine for any composite type.
271 record_out(PG_FUNCTION_ARGS)
273 HeapTupleHeader rec = PG_GETARG_HEAPTUPLEHEADER(0);
278 RecordIOData *my_extra;
279 bool needComma = false;
286 /* Extract type info from the tuple itself */
287 tupType = HeapTupleHeaderGetTypeId(rec);
288 tupTypmod = HeapTupleHeaderGetTypMod(rec);
289 tupdesc = lookup_rowtype_tupdesc(tupType, tupTypmod);
290 ncolumns = tupdesc->natts;
292 /* Build a temporary HeapTuple control structure */
293 tuple.t_len = HeapTupleHeaderGetDatumLength(rec);
294 ItemPointerSetInvalid(&(tuple.t_self));
295 tuple.t_tableOid = InvalidOid;
299 * We arrange to look up the needed I/O info just once per series of
300 * calls, assuming the record type doesn't change underneath us.
302 my_extra = (RecordIOData *) fcinfo->flinfo->fn_extra;
303 if (my_extra == NULL ||
304 my_extra->ncolumns != ncolumns)
306 fcinfo->flinfo->fn_extra =
307 MemoryContextAlloc(fcinfo->flinfo->fn_mcxt,
308 sizeof(RecordIOData) - sizeof(ColumnIOData)
309 + ncolumns * sizeof(ColumnIOData));
310 my_extra = (RecordIOData *) fcinfo->flinfo->fn_extra;
311 my_extra->record_type = InvalidOid;
312 my_extra->record_typmod = 0;
315 if (my_extra->record_type != tupType ||
316 my_extra->record_typmod != tupTypmod)
319 sizeof(RecordIOData) - sizeof(ColumnIOData)
320 + ncolumns * sizeof(ColumnIOData));
321 my_extra->record_type = tupType;
322 my_extra->record_typmod = tupTypmod;
323 my_extra->ncolumns = ncolumns;
326 values = (Datum *) palloc(ncolumns * sizeof(Datum));
327 nulls = (char *) palloc(ncolumns * sizeof(char));
329 /* Break down the tuple into fields */
330 heap_deformtuple(&tuple, tupdesc, values, nulls);
332 /* And build the result string */
333 initStringInfo(&buf);
335 appendStringInfoChar(&buf, '(');
337 for (i = 0; i < ncolumns; i++)
339 ColumnIOData *column_info = &my_extra->columns[i];
340 Oid column_type = tupdesc->attrs[i]->atttypid;
345 /* Ignore dropped columns in datatype */
346 if (tupdesc->attrs[i]->attisdropped)
350 appendStringInfoChar(&buf, ',');
355 /* emit nothing... */
360 * Convert the column value to text
362 if (column_info->column_type != column_type)
366 getTypeOutputInfo(column_type,
367 &column_info->typiofunc,
369 fmgr_info_cxt(column_info->typiofunc, &column_info->proc,
370 fcinfo->flinfo->fn_mcxt);
371 column_info->column_type = column_type;
374 value = DatumGetCString(FunctionCall1(&column_info->proc,
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, ')');
412 PG_RETURN_CSTRING(buf.data);
416 * record_recv - binary input routine for any composite type.
419 record_recv(PG_FUNCTION_ARGS)
421 StringInfo buf = (StringInfo) PG_GETARG_POINTER(0);
422 Oid tupType = PG_GETARG_OID(1);
424 int32 typmod = PG_GETARG_INT32(2);
426 HeapTupleHeader result;
430 RecordIOData *my_extra;
439 * Use the passed type unless it's RECORD; we can't support input of
440 * anonymous types, mainly because there's no good way to figure out
441 * which anonymous type is wanted. Note that for RECORD, what we'll
442 * probably actually get is RECORD's typelem, ie, zero.
444 if (tupType == InvalidOid || tupType == RECORDOID)
446 (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
447 errmsg("input of anonymous composite types is not implemented")));
448 tupTypmod = -1; /* for all non-anonymous types */
449 tupdesc = lookup_rowtype_tupdesc(tupType, tupTypmod);
450 ncolumns = tupdesc->natts;
453 * We arrange to look up the needed I/O info just once per series of
454 * calls, assuming the record type doesn't change underneath us.
456 my_extra = (RecordIOData *) fcinfo->flinfo->fn_extra;
457 if (my_extra == NULL ||
458 my_extra->ncolumns != ncolumns)
460 fcinfo->flinfo->fn_extra =
461 MemoryContextAlloc(fcinfo->flinfo->fn_mcxt,
462 sizeof(RecordIOData) - sizeof(ColumnIOData)
463 + ncolumns * sizeof(ColumnIOData));
464 my_extra = (RecordIOData *) fcinfo->flinfo->fn_extra;
465 my_extra->record_type = InvalidOid;
466 my_extra->record_typmod = 0;
469 if (my_extra->record_type != tupType ||
470 my_extra->record_typmod != tupTypmod)
473 sizeof(RecordIOData) - sizeof(ColumnIOData)
474 + ncolumns * sizeof(ColumnIOData));
475 my_extra->record_type = tupType;
476 my_extra->record_typmod = tupTypmod;
477 my_extra->ncolumns = ncolumns;
480 values = (Datum *) palloc(ncolumns * sizeof(Datum));
481 nulls = (char *) palloc(ncolumns * sizeof(char));
483 /* Fetch number of columns user thinks it has */
484 usercols = pq_getmsgint(buf, 4);
486 /* Need to scan to count nondeleted columns */
488 for (i = 0; i < ncolumns; i++)
490 if (!tupdesc->attrs[i]->attisdropped)
493 if (usercols != validcols)
495 (errcode(ERRCODE_DATATYPE_MISMATCH),
496 errmsg("wrong number of columns: %d, expected %d",
497 usercols, validcols)));
499 /* Process each column */
500 for (i = 0; i < ncolumns; i++)
502 ColumnIOData *column_info = &my_extra->columns[i];
503 Oid column_type = tupdesc->attrs[i]->atttypid;
507 /* Ignore dropped columns in datatype, but fill with nulls */
508 if (tupdesc->attrs[i]->attisdropped)
510 values[i] = (Datum) 0;
515 /* Verify column datatype */
516 coltypoid = pq_getmsgint(buf, sizeof(Oid));
517 if (coltypoid != column_type)
519 (errcode(ERRCODE_DATATYPE_MISMATCH),
520 errmsg("wrong data type: %u, expected %u",
521 coltypoid, column_type)));
523 /* Get and check the item length */
524 itemlen = pq_getmsgint(buf, 4);
525 if (itemlen < -1 || itemlen > (buf->len - buf->cursor))
527 (errcode(ERRCODE_INVALID_BINARY_REPRESENTATION),
528 errmsg("insufficient data left in message")));
532 /* -1 length means NULL */
533 values[i] = (Datum) 0;
539 * Rather than copying data around, we just set up a phony
540 * StringInfo pointing to the correct portion of the input
541 * buffer. We assume we can scribble on the input buffer so as
542 * to maintain the convention that StringInfos have a trailing
545 StringInfoData item_buf;
548 item_buf.data = &buf->data[buf->cursor];
549 item_buf.maxlen = itemlen + 1;
550 item_buf.len = itemlen;
553 buf->cursor += itemlen;
555 csave = buf->data[buf->cursor];
556 buf->data[buf->cursor] = '\0';
558 /* Now call the column's receiveproc */
559 if (column_info->column_type != column_type)
561 getTypeBinaryInputInfo(column_type,
562 &column_info->typiofunc,
563 &column_info->typioparam);
564 fmgr_info_cxt(column_info->typiofunc, &column_info->proc,
565 fcinfo->flinfo->fn_mcxt);
566 column_info->column_type = column_type;
569 values[i] = FunctionCall3(&column_info->proc,
570 PointerGetDatum(&item_buf),
571 ObjectIdGetDatum(column_info->typioparam),
572 Int32GetDatum(tupdesc->attrs[i]->atttypmod));
575 /* Trouble if it didn't eat the whole buffer */
576 if (item_buf.cursor != itemlen)
578 (errcode(ERRCODE_INVALID_BINARY_REPRESENTATION),
579 errmsg("improper binary format in record column %d",
582 buf->data[buf->cursor] = csave;
586 tuple = heap_formtuple(tupdesc, values, nulls);
589 * We cannot return tuple->t_data because heap_formtuple allocates it
590 * as part of a larger chunk, and our caller may expect to be able to
591 * pfree our result. So must copy the info into a new palloc chunk.
593 result = (HeapTupleHeader) palloc(tuple->t_len);
594 memcpy(result, tuple->t_data, tuple->t_len);
596 heap_freetuple(tuple);
600 PG_RETURN_HEAPTUPLEHEADER(result);
604 * record_send - binary output routine for any composite type.
607 record_send(PG_FUNCTION_ARGS)
609 HeapTupleHeader rec = PG_GETARG_HEAPTUPLEHEADER(0);
614 RecordIOData *my_extra;
622 /* Extract type info from the tuple itself */
623 tupType = HeapTupleHeaderGetTypeId(rec);
624 tupTypmod = HeapTupleHeaderGetTypMod(rec);
625 tupdesc = lookup_rowtype_tupdesc(tupType, tupTypmod);
626 ncolumns = tupdesc->natts;
628 /* Build a temporary HeapTuple control structure */
629 tuple.t_len = HeapTupleHeaderGetDatumLength(rec);
630 ItemPointerSetInvalid(&(tuple.t_self));
631 tuple.t_tableOid = InvalidOid;
635 * We arrange to look up the needed I/O info just once per series of
636 * calls, assuming the record type doesn't change underneath us.
638 my_extra = (RecordIOData *) fcinfo->flinfo->fn_extra;
639 if (my_extra == NULL ||
640 my_extra->ncolumns != ncolumns)
642 fcinfo->flinfo->fn_extra =
643 MemoryContextAlloc(fcinfo->flinfo->fn_mcxt,
644 sizeof(RecordIOData) - sizeof(ColumnIOData)
645 + ncolumns * sizeof(ColumnIOData));
646 my_extra = (RecordIOData *) fcinfo->flinfo->fn_extra;
647 my_extra->record_type = InvalidOid;
648 my_extra->record_typmod = 0;
651 if (my_extra->record_type != tupType ||
652 my_extra->record_typmod != tupTypmod)
655 sizeof(RecordIOData) - sizeof(ColumnIOData)
656 + ncolumns * sizeof(ColumnIOData));
657 my_extra->record_type = tupType;
658 my_extra->record_typmod = tupTypmod;
659 my_extra->ncolumns = ncolumns;
662 values = (Datum *) palloc(ncolumns * sizeof(Datum));
663 nulls = (char *) palloc(ncolumns * sizeof(char));
665 /* Break down the tuple into fields */
666 heap_deformtuple(&tuple, tupdesc, values, nulls);
668 /* And build the result string */
669 pq_begintypsend(&buf);
671 /* Need to scan to count nondeleted columns */
673 for (i = 0; i < ncolumns; i++)
675 if (!tupdesc->attrs[i]->attisdropped)
678 pq_sendint(&buf, validcols, 4);
680 for (i = 0; i < ncolumns; i++)
682 ColumnIOData *column_info = &my_extra->columns[i];
683 Oid column_type = tupdesc->attrs[i]->atttypid;
686 /* Ignore dropped columns in datatype */
687 if (tupdesc->attrs[i]->attisdropped)
690 pq_sendint(&buf, column_type, sizeof(Oid));
694 /* emit -1 data length to signify a NULL */
695 pq_sendint(&buf, -1, 4);
700 * Convert the column value to binary
702 if (column_info->column_type != column_type)
706 getTypeBinaryOutputInfo(column_type,
707 &column_info->typiofunc,
709 fmgr_info_cxt(column_info->typiofunc, &column_info->proc,
710 fcinfo->flinfo->fn_mcxt);
711 column_info->column_type = column_type;
714 outputbytes = DatumGetByteaP(FunctionCall1(&column_info->proc,
717 /* We assume the result will not have been toasted */
718 pq_sendint(&buf, VARSIZE(outputbytes) - VARHDRSZ, 4);
719 pq_sendbytes(&buf, VARDATA(outputbytes),
720 VARSIZE(outputbytes) - VARHDRSZ);
727 PG_RETURN_BYTEA_P(pq_endtypsend(&buf));