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.8.4.2 2005-04-30 20:04:46 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,
365
&column_info->typioparam,
367
fmgr_info_cxt(column_info->typiofunc, &column_info->proc,
368
fcinfo->flinfo->fn_mcxt);
369
column_info->column_type = column_type;
372
value = DatumGetCString(FunctionCall3(&column_info->proc,
374
ObjectIdGetDatum(column_info->typioparam),
375
Int32GetDatum(tupdesc->attrs[i]->atttypmod)));
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);
423
HeapTupleHeader result;
427
RecordIOData *my_extra;
436
* Use the passed type unless it's RECORD; we can't support input of
437
* anonymous types, mainly because there's no good way to figure out
438
* which anonymous type is wanted. Note that for RECORD, what we'll
439
* probably actually get is RECORD's typelem, ie, zero.
441
if (tupType == InvalidOid || tupType == RECORDOID)
443
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
444
errmsg("input of anonymous composite types is not implemented")));
445
tupTypmod = -1; /* for all non-anonymous types */
446
tupdesc = lookup_rowtype_tupdesc(tupType, tupTypmod);
447
ncolumns = tupdesc->natts;
450
* We arrange to look up the needed I/O info just once per series of
451
* calls, assuming the record type doesn't change underneath us.
453
my_extra = (RecordIOData *) fcinfo->flinfo->fn_extra;
454
if (my_extra == NULL ||
455
my_extra->ncolumns != ncolumns)
457
fcinfo->flinfo->fn_extra =
458
MemoryContextAlloc(fcinfo->flinfo->fn_mcxt,
459
sizeof(RecordIOData) - sizeof(ColumnIOData)
460
+ ncolumns * sizeof(ColumnIOData));
461
my_extra = (RecordIOData *) fcinfo->flinfo->fn_extra;
462
my_extra->record_type = InvalidOid;
463
my_extra->record_typmod = 0;
466
if (my_extra->record_type != tupType ||
467
my_extra->record_typmod != tupTypmod)
470
sizeof(RecordIOData) - sizeof(ColumnIOData)
471
+ ncolumns * sizeof(ColumnIOData));
472
my_extra->record_type = tupType;
473
my_extra->record_typmod = tupTypmod;
474
my_extra->ncolumns = ncolumns;
477
values = (Datum *) palloc(ncolumns * sizeof(Datum));
478
nulls = (char *) palloc(ncolumns * sizeof(char));
480
/* Fetch number of columns user thinks it has */
481
usercols = pq_getmsgint(buf, 4);
483
/* Need to scan to count nondeleted columns */
485
for (i = 0; i < ncolumns; i++)
487
if (!tupdesc->attrs[i]->attisdropped)
490
if (usercols != validcols)
492
(errcode(ERRCODE_DATATYPE_MISMATCH),
493
errmsg("wrong number of columns: %d, expected %d",
494
usercols, validcols)));
496
/* Process each column */
497
for (i = 0; i < ncolumns; i++)
499
ColumnIOData *column_info = &my_extra->columns[i];
500
Oid column_type = tupdesc->attrs[i]->atttypid;
504
/* Ignore dropped columns in datatype, but fill with nulls */
505
if (tupdesc->attrs[i]->attisdropped)
507
values[i] = (Datum) 0;
512
/* Verify column datatype */
513
coltypoid = pq_getmsgint(buf, sizeof(Oid));
514
if (coltypoid != column_type)
516
(errcode(ERRCODE_DATATYPE_MISMATCH),
517
errmsg("wrong data type: %u, expected %u",
518
coltypoid, column_type)));
520
/* Get and check the item length */
521
itemlen = pq_getmsgint(buf, 4);
522
if (itemlen < -1 || itemlen > (buf->len - buf->cursor))
524
(errcode(ERRCODE_INVALID_BINARY_REPRESENTATION),
525
errmsg("insufficient data left in message")));
529
/* -1 length means NULL */
530
values[i] = (Datum) 0;
536
* Rather than copying data around, we just set up a phony
537
* StringInfo pointing to the correct portion of the input
538
* buffer. We assume we can scribble on the input buffer so as
539
* to maintain the convention that StringInfos have a trailing
542
StringInfoData item_buf;
545
item_buf.data = &buf->data[buf->cursor];
546
item_buf.maxlen = itemlen + 1;
547
item_buf.len = itemlen;
550
buf->cursor += itemlen;
552
csave = buf->data[buf->cursor];
553
buf->data[buf->cursor] = '\0';
555
/* Now call the column's receiveproc */
556
if (column_info->column_type != column_type)
558
getTypeBinaryInputInfo(column_type,
559
&column_info->typiofunc,
560
&column_info->typioparam);
561
fmgr_info_cxt(column_info->typiofunc, &column_info->proc,
562
fcinfo->flinfo->fn_mcxt);
563
column_info->column_type = column_type;
566
values[i] = FunctionCall2(&column_info->proc,
567
PointerGetDatum(&item_buf),
568
ObjectIdGetDatum(column_info->typioparam));
572
/* Trouble if it didn't eat the whole buffer */
573
if (item_buf.cursor != itemlen)
575
(errcode(ERRCODE_INVALID_BINARY_REPRESENTATION),
576
errmsg("improper binary format in record column %d",
579
buf->data[buf->cursor] = csave;
583
tuple = heap_formtuple(tupdesc, values, nulls);
586
* We cannot return tuple->t_data because heap_formtuple allocates it
587
* as part of a larger chunk, and our caller may expect to be able to
588
* pfree our result. So must copy the info into a new palloc chunk.
590
result = (HeapTupleHeader) palloc(tuple->t_len);
591
memcpy(result, tuple->t_data, tuple->t_len);
593
heap_freetuple(tuple);
597
PG_RETURN_HEAPTUPLEHEADER(result);
601
* record_send - binary output routine for any composite type.
604
record_send(PG_FUNCTION_ARGS)
606
HeapTupleHeader rec = PG_GETARG_HEAPTUPLEHEADER(0);
611
RecordIOData *my_extra;
619
/* Extract type info from the tuple itself */
620
tupType = HeapTupleHeaderGetTypeId(rec);
621
tupTypmod = HeapTupleHeaderGetTypMod(rec);
622
tupdesc = lookup_rowtype_tupdesc(tupType, tupTypmod);
623
ncolumns = tupdesc->natts;
625
/* Build a temporary HeapTuple control structure */
626
tuple.t_len = HeapTupleHeaderGetDatumLength(rec);
627
ItemPointerSetInvalid(&(tuple.t_self));
628
tuple.t_tableOid = InvalidOid;
632
* We arrange to look up the needed I/O info just once per series of
633
* calls, assuming the record type doesn't change underneath us.
635
my_extra = (RecordIOData *) fcinfo->flinfo->fn_extra;
636
if (my_extra == NULL ||
637
my_extra->ncolumns != ncolumns)
639
fcinfo->flinfo->fn_extra =
640
MemoryContextAlloc(fcinfo->flinfo->fn_mcxt,
641
sizeof(RecordIOData) - sizeof(ColumnIOData)
642
+ ncolumns * sizeof(ColumnIOData));
643
my_extra = (RecordIOData *) fcinfo->flinfo->fn_extra;
644
my_extra->record_type = InvalidOid;
645
my_extra->record_typmod = 0;
648
if (my_extra->record_type != tupType ||
649
my_extra->record_typmod != tupTypmod)
652
sizeof(RecordIOData) - sizeof(ColumnIOData)
653
+ ncolumns * sizeof(ColumnIOData));
654
my_extra->record_type = tupType;
655
my_extra->record_typmod = tupTypmod;
656
my_extra->ncolumns = ncolumns;
659
values = (Datum *) palloc(ncolumns * sizeof(Datum));
660
nulls = (char *) palloc(ncolumns * sizeof(char));
662
/* Break down the tuple into fields */
663
heap_deformtuple(&tuple, tupdesc, values, nulls);
665
/* And build the result string */
666
pq_begintypsend(&buf);
668
/* Need to scan to count nondeleted columns */
670
for (i = 0; i < ncolumns; i++)
672
if (!tupdesc->attrs[i]->attisdropped)
675
pq_sendint(&buf, validcols, 4);
677
for (i = 0; i < ncolumns; i++)
679
ColumnIOData *column_info = &my_extra->columns[i];
680
Oid column_type = tupdesc->attrs[i]->atttypid;
683
/* Ignore dropped columns in datatype */
684
if (tupdesc->attrs[i]->attisdropped)
687
pq_sendint(&buf, column_type, sizeof(Oid));
691
/* emit -1 data length to signify a NULL */
692
pq_sendint(&buf, -1, 4);
697
* Convert the column value to binary
699
if (column_info->column_type != column_type)
703
getTypeBinaryOutputInfo(column_type,
704
&column_info->typiofunc,
705
&column_info->typioparam,
707
fmgr_info_cxt(column_info->typiofunc, &column_info->proc,
708
fcinfo->flinfo->fn_mcxt);
709
column_info->column_type = column_type;
712
outputbytes = DatumGetByteaP(FunctionCall2(&column_info->proc,
714
ObjectIdGetDatum(column_info->typioparam)));
716
/* We assume the result will not have been toasted */
717
pq_sendint(&buf, VARSIZE(outputbytes) - VARHDRSZ, 4);
718
pq_sendbytes(&buf, VARDATA(outputbytes),
719
VARSIZE(outputbytes) - VARHDRSZ);
726
PG_RETURN_BYTEA_P(pq_endtypsend(&buf));