1 /*-------------------------------------------------------------------------
4 * I/O functions for generic composite types.
6 * Portions Copyright (c) 1996-2004, 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.6 2004/08/29 05:06:49 momjian 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);
60 RecordIOData *my_extra;
61 bool needComma = false;
70 * Use the passed type unless it's RECORD; we can't support input of
71 * anonymous types, mainly because there's no good way to figure out
72 * which anonymous type is wanted. Note that for RECORD, what we'll
73 * probably actually get is RECORD's typelem, ie, zero.
75 if (tupType == InvalidOid || tupType == RECORDOID)
77 (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
78 errmsg("input of anonymous composite types is not implemented")));
79 tupTypmod = -1; /* for all non-anonymous types */
80 tupdesc = lookup_rowtype_tupdesc(tupType, tupTypmod);
81 ncolumns = tupdesc->natts;
84 * We arrange to look up the needed I/O info just once per series of
85 * calls, assuming the record type doesn't change underneath us.
87 my_extra = (RecordIOData *) fcinfo->flinfo->fn_extra;
88 if (my_extra == NULL ||
89 my_extra->ncolumns != ncolumns)
91 fcinfo->flinfo->fn_extra =
92 MemoryContextAlloc(fcinfo->flinfo->fn_mcxt,
93 sizeof(RecordIOData) - sizeof(ColumnIOData)
94 + ncolumns * sizeof(ColumnIOData));
95 my_extra = (RecordIOData *) fcinfo->flinfo->fn_extra;
96 my_extra->record_type = InvalidOid;
97 my_extra->record_typmod = 0;
100 if (my_extra->record_type != tupType ||
101 my_extra->record_typmod != tupTypmod)
104 sizeof(RecordIOData) - sizeof(ColumnIOData)
105 + ncolumns * sizeof(ColumnIOData));
106 my_extra->record_type = tupType;
107 my_extra->record_typmod = tupTypmod;
108 my_extra->ncolumns = ncolumns;
111 values = (Datum *) palloc(ncolumns * sizeof(Datum));
112 nulls = (char *) palloc(ncolumns * sizeof(char));
115 * Scan the string. We use "buf" to accumulate the de-quoted data for
116 * each column, which is then fed to the appropriate input converter.
119 /* Allow leading whitespace */
120 while (*ptr && isspace((unsigned char) *ptr))
124 (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
125 errmsg("malformed record literal: \"%s\"", string),
126 errdetail("Missing left parenthesis.")));
128 initStringInfo(&buf);
130 for (i = 0; i < ncolumns; i++)
132 ColumnIOData *column_info = &my_extra->columns[i];
133 Oid column_type = tupdesc->attrs[i]->atttypid;
135 /* Ignore dropped columns in datatype, but fill with nulls */
136 if (tupdesc->attrs[i]->attisdropped)
138 values[i] = (Datum) 0;
145 /* Skip comma that separates prior field from this one */
149 /* *ptr must be ')' */
151 (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
152 errmsg("malformed record literal: \"%s\"", string),
153 errdetail("Too few columns.")));
156 /* Check for null: completely empty input means null */
157 if (*ptr == ',' || *ptr == ')')
159 values[i] = (Datum) 0;
164 /* Extract string for this column */
165 bool inquote = false;
169 while (inquote || !(*ptr == ',' || *ptr == ')'))
175 (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
176 errmsg("malformed record literal: \"%s\"",
178 errdetail("Unexpected end of input.")));
183 (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
184 errmsg("malformed record literal: \"%s\"",
186 errdetail("Unexpected end of input.")));
187 appendStringInfoChar(&buf, *ptr++);
193 else if (*ptr == '\"')
195 /* doubled quote within quote sequence */
196 appendStringInfoChar(&buf, *ptr++);
202 appendStringInfoChar(&buf, ch);
206 * Convert the column value
208 if (column_info->column_type != column_type)
210 getTypeInputInfo(column_type,
211 &column_info->typiofunc,
212 &column_info->typioparam);
213 fmgr_info_cxt(column_info->typiofunc, &column_info->proc,
214 fcinfo->flinfo->fn_mcxt);
215 column_info->column_type = column_type;
218 values[i] = FunctionCall3(&column_info->proc,
219 CStringGetDatum(buf.data),
220 ObjectIdGetDatum(column_info->typioparam),
221 Int32GetDatum(tupdesc->attrs[i]->atttypmod));
226 * Prep for next column
233 (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
234 errmsg("malformed record literal: \"%s\"", string),
235 errdetail("Too many columns.")));
236 /* Allow trailing whitespace */
237 while (*ptr && isspace((unsigned char) *ptr))
241 (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
242 errmsg("malformed record literal: \"%s\"", string),
243 errdetail("Junk after right parenthesis.")));
245 tuple = heap_formtuple(tupdesc, values, nulls);
251 PG_RETURN_HEAPTUPLEHEADER(tuple->t_data);
255 * record_out - output routine for any composite type.
258 record_out(PG_FUNCTION_ARGS)
260 HeapTupleHeader rec = PG_GETARG_HEAPTUPLEHEADER(0);
261 Oid tupType = PG_GETARG_OID(1);
265 RecordIOData *my_extra;
266 bool needComma = false;
274 * Use the passed type unless it's RECORD; in that case, we'd better
275 * get the type info out of the datum itself. Note that for RECORD,
276 * what we'll probably actually get is RECORD's typelem, ie, zero.
278 if (tupType == InvalidOid || tupType == RECORDOID)
280 tupType = HeapTupleHeaderGetTypeId(rec);
281 tupTypmod = HeapTupleHeaderGetTypMod(rec);
285 tupdesc = lookup_rowtype_tupdesc(tupType, tupTypmod);
286 ncolumns = tupdesc->natts;
288 /* Build a temporary HeapTuple control structure */
289 tuple.t_len = HeapTupleHeaderGetDatumLength(rec);
290 ItemPointerSetInvalid(&(tuple.t_self));
291 tuple.t_tableOid = InvalidOid;
295 * We arrange to look up the needed I/O info just once per series of
296 * calls, assuming the record type doesn't change underneath us.
298 my_extra = (RecordIOData *) fcinfo->flinfo->fn_extra;
299 if (my_extra == NULL ||
300 my_extra->ncolumns != ncolumns)
302 fcinfo->flinfo->fn_extra =
303 MemoryContextAlloc(fcinfo->flinfo->fn_mcxt,
304 sizeof(RecordIOData) - sizeof(ColumnIOData)
305 + ncolumns * sizeof(ColumnIOData));
306 my_extra = (RecordIOData *) fcinfo->flinfo->fn_extra;
307 my_extra->record_type = InvalidOid;
308 my_extra->record_typmod = 0;
311 if (my_extra->record_type != tupType ||
312 my_extra->record_typmod != tupTypmod)
315 sizeof(RecordIOData) - sizeof(ColumnIOData)
316 + ncolumns * sizeof(ColumnIOData));
317 my_extra->record_type = tupType;
318 my_extra->record_typmod = tupTypmod;
319 my_extra->ncolumns = ncolumns;
322 values = (Datum *) palloc(ncolumns * sizeof(Datum));
323 nulls = (char *) palloc(ncolumns * sizeof(char));
325 /* Break down the tuple into fields */
326 heap_deformtuple(&tuple, tupdesc, values, nulls);
328 /* And build the result string */
329 initStringInfo(&buf);
331 appendStringInfoChar(&buf, '(');
333 for (i = 0; i < ncolumns; i++)
335 ColumnIOData *column_info = &my_extra->columns[i];
336 Oid column_type = tupdesc->attrs[i]->atttypid;
341 /* Ignore dropped columns in datatype */
342 if (tupdesc->attrs[i]->attisdropped)
346 appendStringInfoChar(&buf, ',');
351 /* emit nothing... */
356 * Convert the column value to text
358 if (column_info->column_type != column_type)
362 getTypeOutputInfo(column_type,
363 &column_info->typiofunc,
364 &column_info->typioparam,
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(FunctionCall3(&column_info->proc,
373 ObjectIdGetDatum(column_info->typioparam),
374 Int32GetDatum(tupdesc->attrs[i]->atttypmod)));
376 /* Detect whether we need double quotes for this value */
377 nq = (value[0] == '\0'); /* force quotes for empty string */
378 for (tmp = value; *tmp; tmp++)
382 if (ch == '"' || ch == '\\' ||
383 ch == '(' || ch == ')' || ch == ',' ||
384 isspace((unsigned char) ch))
391 /* And emit the string */
393 appendStringInfoChar(&buf, '"');
394 for (tmp = value; *tmp; tmp++)
398 if (ch == '"' || ch == '\\')
399 appendStringInfoChar(&buf, ch);
400 appendStringInfoChar(&buf, ch);
403 appendStringInfoChar(&buf, '"');
406 appendStringInfoChar(&buf, ')');
411 PG_RETURN_CSTRING(buf.data);
415 * record_recv - binary input routine for any composite type.
418 record_recv(PG_FUNCTION_ARGS)
420 StringInfo buf = (StringInfo) PG_GETARG_POINTER(0);
421 Oid tupType = PG_GETARG_OID(1);
425 RecordIOData *my_extra;
434 * Use the passed type unless it's RECORD; we can't support input of
435 * anonymous types, mainly because there's no good way to figure out
436 * which anonymous type is wanted. Note that for RECORD, what we'll
437 * probably actually get is RECORD's typelem, ie, zero.
439 if (tupType == InvalidOid || tupType == RECORDOID)
441 (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
442 errmsg("input of anonymous composite types is not implemented")));
443 tupTypmod = -1; /* for all non-anonymous types */
444 tupdesc = lookup_rowtype_tupdesc(tupType, tupTypmod);
445 ncolumns = tupdesc->natts;
448 * We arrange to look up the needed I/O info just once per series of
449 * calls, assuming the record type doesn't change underneath us.
451 my_extra = (RecordIOData *) fcinfo->flinfo->fn_extra;
452 if (my_extra == NULL ||
453 my_extra->ncolumns != ncolumns)
455 fcinfo->flinfo->fn_extra =
456 MemoryContextAlloc(fcinfo->flinfo->fn_mcxt,
457 sizeof(RecordIOData) - sizeof(ColumnIOData)
458 + ncolumns * sizeof(ColumnIOData));
459 my_extra = (RecordIOData *) fcinfo->flinfo->fn_extra;
460 my_extra->record_type = InvalidOid;
461 my_extra->record_typmod = 0;
464 if (my_extra->record_type != tupType ||
465 my_extra->record_typmod != tupTypmod)
468 sizeof(RecordIOData) - sizeof(ColumnIOData)
469 + ncolumns * sizeof(ColumnIOData));
470 my_extra->record_type = tupType;
471 my_extra->record_typmod = tupTypmod;
472 my_extra->ncolumns = ncolumns;
475 values = (Datum *) palloc(ncolumns * sizeof(Datum));
476 nulls = (char *) palloc(ncolumns * sizeof(char));
478 /* Fetch number of columns user thinks it has */
479 usercols = pq_getmsgint(buf, 4);
481 /* Need to scan to count nondeleted columns */
483 for (i = 0; i < ncolumns; i++)
485 if (!tupdesc->attrs[i]->attisdropped)
488 if (usercols != validcols)
490 (errcode(ERRCODE_DATATYPE_MISMATCH),
491 errmsg("wrong number of columns: %d, expected %d",
492 usercols, validcols)));
494 /* Process each column */
495 for (i = 0; i < ncolumns; i++)
497 ColumnIOData *column_info = &my_extra->columns[i];
498 Oid column_type = tupdesc->attrs[i]->atttypid;
502 /* Ignore dropped columns in datatype, but fill with nulls */
503 if (tupdesc->attrs[i]->attisdropped)
505 values[i] = (Datum) 0;
510 /* Verify column datatype */
511 coltypoid = pq_getmsgint(buf, sizeof(Oid));
512 if (coltypoid != column_type)
514 (errcode(ERRCODE_DATATYPE_MISMATCH),
515 errmsg("wrong data type: %u, expected %u",
516 coltypoid, column_type)));
518 /* Get and check the item length */
519 itemlen = pq_getmsgint(buf, 4);
520 if (itemlen < -1 || itemlen > (buf->len - buf->cursor))
522 (errcode(ERRCODE_INVALID_BINARY_REPRESENTATION),
523 errmsg("insufficient data left in message")));
527 /* -1 length means NULL */
528 values[i] = (Datum) 0;
534 * Rather than copying data around, we just set up a phony
535 * StringInfo pointing to the correct portion of the input
536 * buffer. We assume we can scribble on the input buffer so as
537 * to maintain the convention that StringInfos have a trailing
540 StringInfoData item_buf;
543 item_buf.data = &buf->data[buf->cursor];
544 item_buf.maxlen = itemlen + 1;
545 item_buf.len = itemlen;
548 buf->cursor += itemlen;
550 csave = buf->data[buf->cursor];
551 buf->data[buf->cursor] = '\0';
553 /* Now call the column's receiveproc */
554 if (column_info->column_type != column_type)
556 getTypeBinaryInputInfo(column_type,
557 &column_info->typiofunc,
558 &column_info->typioparam);
559 fmgr_info_cxt(column_info->typiofunc, &column_info->proc,
560 fcinfo->flinfo->fn_mcxt);
561 column_info->column_type = column_type;
564 values[i] = FunctionCall2(&column_info->proc,
565 PointerGetDatum(&item_buf),
566 ObjectIdGetDatum(column_info->typioparam));
570 /* Trouble if it didn't eat the whole buffer */
571 if (item_buf.cursor != itemlen)
573 (errcode(ERRCODE_INVALID_BINARY_REPRESENTATION),
574 errmsg("improper binary format in record column %d",
577 buf->data[buf->cursor] = csave;
581 tuple = heap_formtuple(tupdesc, values, nulls);
586 PG_RETURN_HEAPTUPLEHEADER(tuple->t_data);
590 * record_send - binary output routine for any composite type.
593 record_send(PG_FUNCTION_ARGS)
595 HeapTupleHeader rec = PG_GETARG_HEAPTUPLEHEADER(0);
596 Oid tupType = PG_GETARG_OID(1);
600 RecordIOData *my_extra;
609 * Use the passed type unless it's RECORD; in that case, we'd better
610 * get the type info out of the datum itself. Note that for RECORD,
611 * what we'll probably actually get is RECORD's typelem, ie, zero.
613 if (tupType == InvalidOid || tupType == RECORDOID)
615 tupType = HeapTupleHeaderGetTypeId(rec);
616 tupTypmod = HeapTupleHeaderGetTypMod(rec);
620 tupdesc = lookup_rowtype_tupdesc(tupType, tupTypmod);
621 ncolumns = tupdesc->natts;
623 /* Build a temporary HeapTuple control structure */
624 tuple.t_len = HeapTupleHeaderGetDatumLength(rec);
625 ItemPointerSetInvalid(&(tuple.t_self));
626 tuple.t_tableOid = InvalidOid;
630 * We arrange to look up the needed I/O info just once per series of
631 * calls, assuming the record type doesn't change underneath us.
633 my_extra = (RecordIOData *) fcinfo->flinfo->fn_extra;
634 if (my_extra == NULL ||
635 my_extra->ncolumns != ncolumns)
637 fcinfo->flinfo->fn_extra =
638 MemoryContextAlloc(fcinfo->flinfo->fn_mcxt,
639 sizeof(RecordIOData) - sizeof(ColumnIOData)
640 + ncolumns * sizeof(ColumnIOData));
641 my_extra = (RecordIOData *) fcinfo->flinfo->fn_extra;
642 my_extra->record_type = InvalidOid;
643 my_extra->record_typmod = 0;
646 if (my_extra->record_type != tupType ||
647 my_extra->record_typmod != tupTypmod)
650 sizeof(RecordIOData) - sizeof(ColumnIOData)
651 + ncolumns * sizeof(ColumnIOData));
652 my_extra->record_type = tupType;
653 my_extra->record_typmod = tupTypmod;
654 my_extra->ncolumns = ncolumns;
657 values = (Datum *) palloc(ncolumns * sizeof(Datum));
658 nulls = (char *) palloc(ncolumns * sizeof(char));
660 /* Break down the tuple into fields */
661 heap_deformtuple(&tuple, tupdesc, values, nulls);
663 /* And build the result string */
664 pq_begintypsend(&buf);
666 /* Need to scan to count nondeleted columns */
668 for (i = 0; i < ncolumns; i++)
670 if (!tupdesc->attrs[i]->attisdropped)
673 pq_sendint(&buf, validcols, 4);
675 for (i = 0; i < ncolumns; i++)
677 ColumnIOData *column_info = &my_extra->columns[i];
678 Oid column_type = tupdesc->attrs[i]->atttypid;
681 /* Ignore dropped columns in datatype */
682 if (tupdesc->attrs[i]->attisdropped)
685 pq_sendint(&buf, column_type, sizeof(Oid));
689 /* emit -1 data length to signify a NULL */
690 pq_sendint(&buf, -1, 4);
695 * Convert the column value to binary
697 if (column_info->column_type != column_type)
701 getTypeBinaryOutputInfo(column_type,
702 &column_info->typiofunc,
703 &column_info->typioparam,
705 fmgr_info_cxt(column_info->typiofunc, &column_info->proc,
706 fcinfo->flinfo->fn_mcxt);
707 column_info->column_type = column_type;
710 outputbytes = DatumGetByteaP(FunctionCall2(&column_info->proc,
712 ObjectIdGetDatum(column_info->typioparam)));
714 /* We assume the result will not have been toasted */
715 pq_sendint(&buf, VARSIZE(outputbytes) - VARHDRSZ, 4);
716 pq_sendbytes(&buf, VARDATA(outputbytes),
717 VARSIZE(outputbytes) - VARHDRSZ);
724 PG_RETURN_BYTEA_P(pq_endtypsend(&buf));