]> granicus.if.org Git - postgresql/blob - src/backend/utils/adt/rowtypes.c
Update copyright notices for year 2012.
[postgresql] / src / backend / utils / adt / rowtypes.c
1 /*-------------------------------------------------------------------------
2  *
3  * rowtypes.c
4  *        I/O and comparison functions for generic composite types.
5  *
6  * Portions Copyright (c) 1996-2012, PostgreSQL Global Development Group
7  * Portions Copyright (c) 1994, Regents of the University of California
8  *
9  *
10  * IDENTIFICATION
11  *        src/backend/utils/adt/rowtypes.c
12  *
13  *-------------------------------------------------------------------------
14  */
15 #include "postgres.h"
16
17 #include <ctype.h>
18
19 #include "catalog/pg_type.h"
20 #include "libpq/pqformat.h"
21 #include "utils/builtins.h"
22 #include "utils/lsyscache.h"
23 #include "utils/typcache.h"
24
25
26 /*
27  * structure to cache metadata needed for record I/O
28  */
29 typedef struct ColumnIOData
30 {
31         Oid                     column_type;
32         Oid                     typiofunc;
33         Oid                     typioparam;
34         FmgrInfo        proc;
35 } ColumnIOData;
36
37 typedef struct RecordIOData
38 {
39         Oid                     record_type;
40         int32           record_typmod;
41         int                     ncolumns;
42         ColumnIOData columns[1];        /* VARIABLE LENGTH ARRAY */
43 } RecordIOData;
44
45 /*
46  * structure to cache metadata needed for record comparison
47  */
48 typedef struct ColumnCompareData
49 {
50         TypeCacheEntry *typentry;       /* has everything we need, actually */
51 } ColumnCompareData;
52
53 typedef struct RecordCompareData
54 {
55         int                     ncolumns;               /* allocated length of columns[] */
56         Oid                     record1_type;
57         int32           record1_typmod;
58         Oid                     record2_type;
59         int32           record2_typmod;
60         ColumnCompareData columns[1];           /* VARIABLE LENGTH ARRAY */
61 } RecordCompareData;
62
63
64 /*
65  * record_in            - input routine for any composite type.
66  */
67 Datum
68 record_in(PG_FUNCTION_ARGS)
69 {
70         char       *string = PG_GETARG_CSTRING(0);
71         Oid                     tupType = PG_GETARG_OID(1);
72
73 #ifdef NOT_USED
74         int32           typmod = PG_GETARG_INT32(2);
75 #endif
76         HeapTupleHeader result;
77         int32           tupTypmod;
78         TupleDesc       tupdesc;
79         HeapTuple       tuple;
80         RecordIOData *my_extra;
81         bool            needComma = false;
82         int                     ncolumns;
83         int                     i;
84         char       *ptr;
85         Datum      *values;
86         bool       *nulls;
87         StringInfoData buf;
88
89         /*
90          * Use the passed type unless it's RECORD; we can't support input of
91          * anonymous types, mainly because there's no good way to figure out which
92          * anonymous type is wanted.  Note that for RECORD, what we'll probably
93          * actually get is RECORD's typelem, ie, zero.
94          */
95         if (tupType == InvalidOid || tupType == RECORDOID)
96                 ereport(ERROR,
97                                 (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
98                    errmsg("input of anonymous composite types is not implemented")));
99         tupTypmod = -1;                         /* for all non-anonymous types */
100
101         /*
102          * This comes from the composite type's pg_type.oid and stores system oids
103          * in user tables, specifically DatumTupleFields. This oid must be
104          * preserved by binary upgrades.
105          */
106         tupdesc = lookup_rowtype_tupdesc(tupType, tupTypmod);
107         ncolumns = tupdesc->natts;
108
109         /*
110          * We arrange to look up the needed I/O info just once per series of
111          * calls, assuming the record type doesn't change underneath us.
112          */
113         my_extra = (RecordIOData *) fcinfo->flinfo->fn_extra;
114         if (my_extra == NULL ||
115                 my_extra->ncolumns != ncolumns)
116         {
117                 fcinfo->flinfo->fn_extra =
118                         MemoryContextAlloc(fcinfo->flinfo->fn_mcxt,
119                                                            sizeof(RecordIOData) - sizeof(ColumnIOData)
120                                                            + ncolumns * sizeof(ColumnIOData));
121                 my_extra = (RecordIOData *) fcinfo->flinfo->fn_extra;
122                 my_extra->record_type = InvalidOid;
123                 my_extra->record_typmod = 0;
124         }
125
126         if (my_extra->record_type != tupType ||
127                 my_extra->record_typmod != tupTypmod)
128         {
129                 MemSet(my_extra, 0,
130                            sizeof(RecordIOData) - sizeof(ColumnIOData)
131                            + ncolumns * sizeof(ColumnIOData));
132                 my_extra->record_type = tupType;
133                 my_extra->record_typmod = tupTypmod;
134                 my_extra->ncolumns = ncolumns;
135         }
136
137         values = (Datum *) palloc(ncolumns * sizeof(Datum));
138         nulls = (bool *) palloc(ncolumns * sizeof(bool));
139
140         /*
141          * Scan the string.  We use "buf" to accumulate the de-quoted data for
142          * each column, which is then fed to the appropriate input converter.
143          */
144         ptr = string;
145         /* Allow leading whitespace */
146         while (*ptr && isspace((unsigned char) *ptr))
147                 ptr++;
148         if (*ptr++ != '(')
149                 ereport(ERROR,
150                                 (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
151                                  errmsg("malformed record literal: \"%s\"", string),
152                                  errdetail("Missing left parenthesis.")));
153
154         initStringInfo(&buf);
155
156         for (i = 0; i < ncolumns; i++)
157         {
158                 ColumnIOData *column_info = &my_extra->columns[i];
159                 Oid                     column_type = tupdesc->attrs[i]->atttypid;
160                 char       *column_data;
161
162                 /* Ignore dropped columns in datatype, but fill with nulls */
163                 if (tupdesc->attrs[i]->attisdropped)
164                 {
165                         values[i] = (Datum) 0;
166                         nulls[i] = true;
167                         continue;
168                 }
169
170                 if (needComma)
171                 {
172                         /* Skip comma that separates prior field from this one */
173                         if (*ptr == ',')
174                                 ptr++;
175                         else
176                                 /* *ptr must be ')' */
177                                 ereport(ERROR,
178                                                 (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
179                                                  errmsg("malformed record literal: \"%s\"", string),
180                                                  errdetail("Too few columns.")));
181                 }
182
183                 /* Check for null: completely empty input means null */
184                 if (*ptr == ',' || *ptr == ')')
185                 {
186                         column_data = NULL;
187                         nulls[i] = true;
188                 }
189                 else
190                 {
191                         /* Extract string for this column */
192                         bool            inquote = false;
193
194                         resetStringInfo(&buf);
195                         while (inquote || !(*ptr == ',' || *ptr == ')'))
196                         {
197                                 char            ch = *ptr++;
198
199                                 if (ch == '\0')
200                                         ereport(ERROR,
201                                                         (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
202                                                          errmsg("malformed record literal: \"%s\"",
203                                                                         string),
204                                                          errdetail("Unexpected end of input.")));
205                                 if (ch == '\\')
206                                 {
207                                         if (*ptr == '\0')
208                                                 ereport(ERROR,
209                                                                 (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
210                                                                  errmsg("malformed record literal: \"%s\"",
211                                                                                 string),
212                                                                  errdetail("Unexpected end of input.")));
213                                         appendStringInfoChar(&buf, *ptr++);
214                                 }
215                                 else if (ch == '\"')
216                                 {
217                                         if (!inquote)
218                                                 inquote = true;
219                                         else if (*ptr == '\"')
220                                         {
221                                                 /* doubled quote within quote sequence */
222                                                 appendStringInfoChar(&buf, *ptr++);
223                                         }
224                                         else
225                                                 inquote = false;
226                                 }
227                                 else
228                                         appendStringInfoChar(&buf, ch);
229                         }
230
231                         column_data = buf.data;
232                         nulls[i] = false;
233                 }
234
235                 /*
236                  * Convert the column value
237                  */
238                 if (column_info->column_type != column_type)
239                 {
240                         getTypeInputInfo(column_type,
241                                                          &column_info->typiofunc,
242                                                          &column_info->typioparam);
243                         fmgr_info_cxt(column_info->typiofunc, &column_info->proc,
244                                                   fcinfo->flinfo->fn_mcxt);
245                         column_info->column_type = column_type;
246                 }
247
248                 values[i] = InputFunctionCall(&column_info->proc,
249                                                                           column_data,
250                                                                           column_info->typioparam,
251                                                                           tupdesc->attrs[i]->atttypmod);
252
253                 /*
254                  * Prep for next column
255                  */
256                 needComma = true;
257         }
258
259         if (*ptr++ != ')')
260                 ereport(ERROR,
261                                 (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
262                                  errmsg("malformed record literal: \"%s\"", string),
263                                  errdetail("Too many columns.")));
264         /* Allow trailing whitespace */
265         while (*ptr && isspace((unsigned char) *ptr))
266                 ptr++;
267         if (*ptr)
268                 ereport(ERROR,
269                                 (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
270                                  errmsg("malformed record literal: \"%s\"", string),
271                                  errdetail("Junk after right parenthesis.")));
272
273         tuple = heap_form_tuple(tupdesc, values, nulls);
274
275         /*
276          * We cannot return tuple->t_data because heap_form_tuple allocates it as
277          * part of a larger chunk, and our caller may expect to be able to pfree
278          * our result.  So must copy the info into a new palloc chunk.
279          */
280         result = (HeapTupleHeader) palloc(tuple->t_len);
281         memcpy(result, tuple->t_data, tuple->t_len);
282
283         heap_freetuple(tuple);
284         pfree(buf.data);
285         pfree(values);
286         pfree(nulls);
287         ReleaseTupleDesc(tupdesc);
288
289         PG_RETURN_HEAPTUPLEHEADER(result);
290 }
291
292 /*
293  * record_out           - output routine for any composite type.
294  */
295 Datum
296 record_out(PG_FUNCTION_ARGS)
297 {
298         HeapTupleHeader rec = PG_GETARG_HEAPTUPLEHEADER(0);
299         Oid                     tupType;
300         int32           tupTypmod;
301         TupleDesc       tupdesc;
302         HeapTupleData tuple;
303         RecordIOData *my_extra;
304         bool            needComma = false;
305         int                     ncolumns;
306         int                     i;
307         Datum      *values;
308         bool       *nulls;
309         StringInfoData buf;
310
311         /* Extract type info from the tuple itself */
312         tupType = HeapTupleHeaderGetTypeId(rec);
313         tupTypmod = HeapTupleHeaderGetTypMod(rec);
314         tupdesc = lookup_rowtype_tupdesc(tupType, tupTypmod);
315         ncolumns = tupdesc->natts;
316
317         /* Build a temporary HeapTuple control structure */
318         tuple.t_len = HeapTupleHeaderGetDatumLength(rec);
319         ItemPointerSetInvalid(&(tuple.t_self));
320         tuple.t_tableOid = InvalidOid;
321         tuple.t_data = rec;
322
323         /*
324          * We arrange to look up the needed I/O info just once per series of
325          * calls, assuming the record type doesn't change underneath us.
326          */
327         my_extra = (RecordIOData *) fcinfo->flinfo->fn_extra;
328         if (my_extra == NULL ||
329                 my_extra->ncolumns != ncolumns)
330         {
331                 fcinfo->flinfo->fn_extra =
332                         MemoryContextAlloc(fcinfo->flinfo->fn_mcxt,
333                                                            sizeof(RecordIOData) - sizeof(ColumnIOData)
334                                                            + ncolumns * sizeof(ColumnIOData));
335                 my_extra = (RecordIOData *) fcinfo->flinfo->fn_extra;
336                 my_extra->record_type = InvalidOid;
337                 my_extra->record_typmod = 0;
338         }
339
340         if (my_extra->record_type != tupType ||
341                 my_extra->record_typmod != tupTypmod)
342         {
343                 MemSet(my_extra, 0,
344                            sizeof(RecordIOData) - sizeof(ColumnIOData)
345                            + ncolumns * sizeof(ColumnIOData));
346                 my_extra->record_type = tupType;
347                 my_extra->record_typmod = tupTypmod;
348                 my_extra->ncolumns = ncolumns;
349         }
350
351         values = (Datum *) palloc(ncolumns * sizeof(Datum));
352         nulls = (bool *) palloc(ncolumns * sizeof(bool));
353
354         /* Break down the tuple into fields */
355         heap_deform_tuple(&tuple, tupdesc, values, nulls);
356
357         /* And build the result string */
358         initStringInfo(&buf);
359
360         appendStringInfoChar(&buf, '(');
361
362         for (i = 0; i < ncolumns; i++)
363         {
364                 ColumnIOData *column_info = &my_extra->columns[i];
365                 Oid                     column_type = tupdesc->attrs[i]->atttypid;
366                 char       *value;
367                 char       *tmp;
368                 bool            nq;
369
370                 /* Ignore dropped columns in datatype */
371                 if (tupdesc->attrs[i]->attisdropped)
372                         continue;
373
374                 if (needComma)
375                         appendStringInfoChar(&buf, ',');
376                 needComma = true;
377
378                 if (nulls[i])
379                 {
380                         /* emit nothing... */
381                         continue;
382                 }
383
384                 /*
385                  * Convert the column value to text
386                  */
387                 if (column_info->column_type != column_type)
388                 {
389                         bool            typIsVarlena;
390
391                         getTypeOutputInfo(column_type,
392                                                           &column_info->typiofunc,
393                                                           &typIsVarlena);
394                         fmgr_info_cxt(column_info->typiofunc, &column_info->proc,
395                                                   fcinfo->flinfo->fn_mcxt);
396                         column_info->column_type = column_type;
397                 }
398
399                 value = OutputFunctionCall(&column_info->proc, values[i]);
400
401                 /* Detect whether we need double quotes for this value */
402                 nq = (value[0] == '\0');        /* force quotes for empty string */
403                 for (tmp = value; *tmp; tmp++)
404                 {
405                         char            ch = *tmp;
406
407                         if (ch == '"' || ch == '\\' ||
408                                 ch == '(' || ch == ')' || ch == ',' ||
409                                 isspace((unsigned char) ch))
410                         {
411                                 nq = true;
412                                 break;
413                         }
414                 }
415
416                 /* And emit the string */
417                 if (nq)
418                         appendStringInfoChar(&buf, '"');
419                 for (tmp = value; *tmp; tmp++)
420                 {
421                         char            ch = *tmp;
422
423                         if (ch == '"' || ch == '\\')
424                                 appendStringInfoChar(&buf, ch);
425                         appendStringInfoChar(&buf, ch);
426                 }
427                 if (nq)
428                         appendStringInfoChar(&buf, '"');
429         }
430
431         appendStringInfoChar(&buf, ')');
432
433         pfree(values);
434         pfree(nulls);
435         ReleaseTupleDesc(tupdesc);
436
437         PG_RETURN_CSTRING(buf.data);
438 }
439
440 /*
441  * record_recv          - binary input routine for any composite type.
442  */
443 Datum
444 record_recv(PG_FUNCTION_ARGS)
445 {
446         StringInfo      buf = (StringInfo) PG_GETARG_POINTER(0);
447         Oid                     tupType = PG_GETARG_OID(1);
448
449 #ifdef NOT_USED
450         int32           typmod = PG_GETARG_INT32(2);
451 #endif
452         HeapTupleHeader result;
453         int32           tupTypmod;
454         TupleDesc       tupdesc;
455         HeapTuple       tuple;
456         RecordIOData *my_extra;
457         int                     ncolumns;
458         int                     usercols;
459         int                     validcols;
460         int                     i;
461         Datum      *values;
462         bool       *nulls;
463
464         /*
465          * Use the passed type unless it's RECORD; we can't support input of
466          * anonymous types, mainly because there's no good way to figure out which
467          * anonymous type is wanted.  Note that for RECORD, what we'll probably
468          * actually get is RECORD's typelem, ie, zero.
469          */
470         if (tupType == InvalidOid || tupType == RECORDOID)
471                 ereport(ERROR,
472                                 (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
473                    errmsg("input of anonymous composite types is not implemented")));
474         tupTypmod = -1;                         /* for all non-anonymous types */
475         tupdesc = lookup_rowtype_tupdesc(tupType, tupTypmod);
476         ncolumns = tupdesc->natts;
477
478         /*
479          * We arrange to look up the needed I/O info just once per series of
480          * calls, assuming the record type doesn't change underneath us.
481          */
482         my_extra = (RecordIOData *) fcinfo->flinfo->fn_extra;
483         if (my_extra == NULL ||
484                 my_extra->ncolumns != ncolumns)
485         {
486                 fcinfo->flinfo->fn_extra =
487                         MemoryContextAlloc(fcinfo->flinfo->fn_mcxt,
488                                                            sizeof(RecordIOData) - sizeof(ColumnIOData)
489                                                            + ncolumns * sizeof(ColumnIOData));
490                 my_extra = (RecordIOData *) fcinfo->flinfo->fn_extra;
491                 my_extra->record_type = InvalidOid;
492                 my_extra->record_typmod = 0;
493         }
494
495         if (my_extra->record_type != tupType ||
496                 my_extra->record_typmod != tupTypmod)
497         {
498                 MemSet(my_extra, 0,
499                            sizeof(RecordIOData) - sizeof(ColumnIOData)
500                            + ncolumns * sizeof(ColumnIOData));
501                 my_extra->record_type = tupType;
502                 my_extra->record_typmod = tupTypmod;
503                 my_extra->ncolumns = ncolumns;
504         }
505
506         values = (Datum *) palloc(ncolumns * sizeof(Datum));
507         nulls = (bool *) palloc(ncolumns * sizeof(bool));
508
509         /* Fetch number of columns user thinks it has */
510         usercols = pq_getmsgint(buf, 4);
511
512         /* Need to scan to count nondeleted columns */
513         validcols = 0;
514         for (i = 0; i < ncolumns; i++)
515         {
516                 if (!tupdesc->attrs[i]->attisdropped)
517                         validcols++;
518         }
519         if (usercols != validcols)
520                 ereport(ERROR,
521                                 (errcode(ERRCODE_DATATYPE_MISMATCH),
522                                  errmsg("wrong number of columns: %d, expected %d",
523                                                 usercols, validcols)));
524
525         /* Process each column */
526         for (i = 0; i < ncolumns; i++)
527         {
528                 ColumnIOData *column_info = &my_extra->columns[i];
529                 Oid                     column_type = tupdesc->attrs[i]->atttypid;
530                 Oid                     coltypoid;
531                 int                     itemlen;
532                 StringInfoData item_buf;
533                 StringInfo      bufptr;
534                 char            csave;
535
536                 /* Ignore dropped columns in datatype, but fill with nulls */
537                 if (tupdesc->attrs[i]->attisdropped)
538                 {
539                         values[i] = (Datum) 0;
540                         nulls[i] = true;
541                         continue;
542                 }
543
544                 /* Verify column datatype */
545                 coltypoid = pq_getmsgint(buf, sizeof(Oid));
546                 if (coltypoid != column_type)
547                         ereport(ERROR,
548                                         (errcode(ERRCODE_DATATYPE_MISMATCH),
549                                          errmsg("wrong data type: %u, expected %u",
550                                                         coltypoid, column_type)));
551
552                 /* Get and check the item length */
553                 itemlen = pq_getmsgint(buf, 4);
554                 if (itemlen < -1 || itemlen > (buf->len - buf->cursor))
555                         ereport(ERROR,
556                                         (errcode(ERRCODE_INVALID_BINARY_REPRESENTATION),
557                                          errmsg("insufficient data left in message")));
558
559                 if (itemlen == -1)
560                 {
561                         /* -1 length means NULL */
562                         bufptr = NULL;
563                         nulls[i] = true;
564                         csave = 0;                      /* keep compiler quiet */
565                 }
566                 else
567                 {
568                         /*
569                          * Rather than copying data around, we just set up a phony
570                          * StringInfo pointing to the correct portion of the input buffer.
571                          * We assume we can scribble on the input buffer so as to maintain
572                          * the convention that StringInfos have a trailing null.
573                          */
574                         item_buf.data = &buf->data[buf->cursor];
575                         item_buf.maxlen = itemlen + 1;
576                         item_buf.len = itemlen;
577                         item_buf.cursor = 0;
578
579                         buf->cursor += itemlen;
580
581                         csave = buf->data[buf->cursor];
582                         buf->data[buf->cursor] = '\0';
583
584                         bufptr = &item_buf;
585                         nulls[i] = false;
586                 }
587
588                 /* Now call the column's receiveproc */
589                 if (column_info->column_type != column_type)
590                 {
591                         getTypeBinaryInputInfo(column_type,
592                                                                    &column_info->typiofunc,
593                                                                    &column_info->typioparam);
594                         fmgr_info_cxt(column_info->typiofunc, &column_info->proc,
595                                                   fcinfo->flinfo->fn_mcxt);
596                         column_info->column_type = column_type;
597                 }
598
599                 values[i] = ReceiveFunctionCall(&column_info->proc,
600                                                                                 bufptr,
601                                                                                 column_info->typioparam,
602                                                                                 tupdesc->attrs[i]->atttypmod);
603
604                 if (bufptr)
605                 {
606                         /* Trouble if it didn't eat the whole buffer */
607                         if (item_buf.cursor != itemlen)
608                                 ereport(ERROR,
609                                                 (errcode(ERRCODE_INVALID_BINARY_REPRESENTATION),
610                                                  errmsg("improper binary format in record column %d",
611                                                                 i + 1)));
612
613                         buf->data[buf->cursor] = csave;
614                 }
615         }
616
617         tuple = heap_form_tuple(tupdesc, values, nulls);
618
619         /*
620          * We cannot return tuple->t_data because heap_form_tuple allocates it as
621          * part of a larger chunk, and our caller may expect to be able to pfree
622          * our result.  So must copy the info into a new palloc chunk.
623          */
624         result = (HeapTupleHeader) palloc(tuple->t_len);
625         memcpy(result, tuple->t_data, tuple->t_len);
626
627         heap_freetuple(tuple);
628         pfree(values);
629         pfree(nulls);
630         ReleaseTupleDesc(tupdesc);
631
632         PG_RETURN_HEAPTUPLEHEADER(result);
633 }
634
635 /*
636  * record_send          - binary output routine for any composite type.
637  */
638 Datum
639 record_send(PG_FUNCTION_ARGS)
640 {
641         HeapTupleHeader rec = PG_GETARG_HEAPTUPLEHEADER(0);
642         Oid                     tupType;
643         int32           tupTypmod;
644         TupleDesc       tupdesc;
645         HeapTupleData tuple;
646         RecordIOData *my_extra;
647         int                     ncolumns;
648         int                     validcols;
649         int                     i;
650         Datum      *values;
651         bool       *nulls;
652         StringInfoData buf;
653
654         /* Extract type info from the tuple itself */
655         tupType = HeapTupleHeaderGetTypeId(rec);
656         tupTypmod = HeapTupleHeaderGetTypMod(rec);
657         tupdesc = lookup_rowtype_tupdesc(tupType, tupTypmod);
658         ncolumns = tupdesc->natts;
659
660         /* Build a temporary HeapTuple control structure */
661         tuple.t_len = HeapTupleHeaderGetDatumLength(rec);
662         ItemPointerSetInvalid(&(tuple.t_self));
663         tuple.t_tableOid = InvalidOid;
664         tuple.t_data = rec;
665
666         /*
667          * We arrange to look up the needed I/O info just once per series of
668          * calls, assuming the record type doesn't change underneath us.
669          */
670         my_extra = (RecordIOData *) fcinfo->flinfo->fn_extra;
671         if (my_extra == NULL ||
672                 my_extra->ncolumns != ncolumns)
673         {
674                 fcinfo->flinfo->fn_extra =
675                         MemoryContextAlloc(fcinfo->flinfo->fn_mcxt,
676                                                            sizeof(RecordIOData) - sizeof(ColumnIOData)
677                                                            + ncolumns * sizeof(ColumnIOData));
678                 my_extra = (RecordIOData *) fcinfo->flinfo->fn_extra;
679                 my_extra->record_type = InvalidOid;
680                 my_extra->record_typmod = 0;
681         }
682
683         if (my_extra->record_type != tupType ||
684                 my_extra->record_typmod != tupTypmod)
685         {
686                 MemSet(my_extra, 0,
687                            sizeof(RecordIOData) - sizeof(ColumnIOData)
688                            + ncolumns * sizeof(ColumnIOData));
689                 my_extra->record_type = tupType;
690                 my_extra->record_typmod = tupTypmod;
691                 my_extra->ncolumns = ncolumns;
692         }
693
694         values = (Datum *) palloc(ncolumns * sizeof(Datum));
695         nulls = (bool *) palloc(ncolumns * sizeof(bool));
696
697         /* Break down the tuple into fields */
698         heap_deform_tuple(&tuple, tupdesc, values, nulls);
699
700         /* And build the result string */
701         pq_begintypsend(&buf);
702
703         /* Need to scan to count nondeleted columns */
704         validcols = 0;
705         for (i = 0; i < ncolumns; i++)
706         {
707                 if (!tupdesc->attrs[i]->attisdropped)
708                         validcols++;
709         }
710         pq_sendint(&buf, validcols, 4);
711
712         for (i = 0; i < ncolumns; i++)
713         {
714                 ColumnIOData *column_info = &my_extra->columns[i];
715                 Oid                     column_type = tupdesc->attrs[i]->atttypid;
716                 bytea      *outputbytes;
717
718                 /* Ignore dropped columns in datatype */
719                 if (tupdesc->attrs[i]->attisdropped)
720                         continue;
721
722                 pq_sendint(&buf, column_type, sizeof(Oid));
723
724                 if (nulls[i])
725                 {
726                         /* emit -1 data length to signify a NULL */
727                         pq_sendint(&buf, -1, 4);
728                         continue;
729                 }
730
731                 /*
732                  * Convert the column value to binary
733                  */
734                 if (column_info->column_type != column_type)
735                 {
736                         bool            typIsVarlena;
737
738                         getTypeBinaryOutputInfo(column_type,
739                                                                         &column_info->typiofunc,
740                                                                         &typIsVarlena);
741                         fmgr_info_cxt(column_info->typiofunc, &column_info->proc,
742                                                   fcinfo->flinfo->fn_mcxt);
743                         column_info->column_type = column_type;
744                 }
745
746                 outputbytes = SendFunctionCall(&column_info->proc, values[i]);
747
748                 /* We assume the result will not have been toasted */
749                 pq_sendint(&buf, VARSIZE(outputbytes) - VARHDRSZ, 4);
750                 pq_sendbytes(&buf, VARDATA(outputbytes),
751                                          VARSIZE(outputbytes) - VARHDRSZ);
752                 pfree(outputbytes);
753         }
754
755         pfree(values);
756         pfree(nulls);
757         ReleaseTupleDesc(tupdesc);
758
759         PG_RETURN_BYTEA_P(pq_endtypsend(&buf));
760 }
761
762
763 /*
764  * record_cmp()
765  * Internal comparison function for records.
766  *
767  * Returns -1, 0 or 1
768  *
769  * Do not assume that the two inputs are exactly the same record type;
770  * for instance we might be comparing an anonymous ROW() construct against a
771  * named composite type.  We will compare as long as they have the same number
772  * of non-dropped columns of the same types.
773  */
774 static int
775 record_cmp(FunctionCallInfo fcinfo)
776 {
777         HeapTupleHeader record1 = PG_GETARG_HEAPTUPLEHEADER(0);
778         HeapTupleHeader record2 = PG_GETARG_HEAPTUPLEHEADER(1);
779         int                     result = 0;
780         Oid                     tupType1;
781         Oid                     tupType2;
782         int32           tupTypmod1;
783         int32           tupTypmod2;
784         TupleDesc       tupdesc1;
785         TupleDesc       tupdesc2;
786         HeapTupleData tuple1;
787         HeapTupleData tuple2;
788         int                     ncolumns1;
789         int                     ncolumns2;
790         RecordCompareData *my_extra;
791         int                     ncols;
792         Datum      *values1;
793         Datum      *values2;
794         bool       *nulls1;
795         bool       *nulls2;
796         int                     i1;
797         int                     i2;
798         int                     j;
799
800         /* Extract type info from the tuples */
801         tupType1 = HeapTupleHeaderGetTypeId(record1);
802         tupTypmod1 = HeapTupleHeaderGetTypMod(record1);
803         tupdesc1 = lookup_rowtype_tupdesc(tupType1, tupTypmod1);
804         ncolumns1 = tupdesc1->natts;
805         tupType2 = HeapTupleHeaderGetTypeId(record2);
806         tupTypmod2 = HeapTupleHeaderGetTypMod(record2);
807         tupdesc2 = lookup_rowtype_tupdesc(tupType2, tupTypmod2);
808         ncolumns2 = tupdesc2->natts;
809
810         /* Build temporary HeapTuple control structures */
811         tuple1.t_len = HeapTupleHeaderGetDatumLength(record1);
812         ItemPointerSetInvalid(&(tuple1.t_self));
813         tuple1.t_tableOid = InvalidOid;
814         tuple1.t_data = record1;
815         tuple2.t_len = HeapTupleHeaderGetDatumLength(record2);
816         ItemPointerSetInvalid(&(tuple2.t_self));
817         tuple2.t_tableOid = InvalidOid;
818         tuple2.t_data = record2;
819
820         /*
821          * We arrange to look up the needed comparison info just once per series
822          * of calls, assuming the record types don't change underneath us.
823          */
824         ncols = Max(ncolumns1, ncolumns2);
825         my_extra = (RecordCompareData *) fcinfo->flinfo->fn_extra;
826         if (my_extra == NULL ||
827                 my_extra->ncolumns < ncols)
828         {
829                 fcinfo->flinfo->fn_extra =
830                         MemoryContextAlloc(fcinfo->flinfo->fn_mcxt,
831                                                 sizeof(RecordCompareData) - sizeof(ColumnCompareData)
832                                                            + ncols * sizeof(ColumnCompareData));
833                 my_extra = (RecordCompareData *) fcinfo->flinfo->fn_extra;
834                 my_extra->ncolumns = ncols;
835                 my_extra->record1_type = InvalidOid;
836                 my_extra->record1_typmod = 0;
837                 my_extra->record2_type = InvalidOid;
838                 my_extra->record2_typmod = 0;
839         }
840
841         if (my_extra->record1_type != tupType1 ||
842                 my_extra->record1_typmod != tupTypmod1 ||
843                 my_extra->record2_type != tupType2 ||
844                 my_extra->record2_typmod != tupTypmod2)
845         {
846                 MemSet(my_extra->columns, 0, ncols * sizeof(ColumnCompareData));
847                 my_extra->record1_type = tupType1;
848                 my_extra->record1_typmod = tupTypmod1;
849                 my_extra->record2_type = tupType2;
850                 my_extra->record2_typmod = tupTypmod2;
851         }
852
853         /* Break down the tuples into fields */
854         values1 = (Datum *) palloc(ncolumns1 * sizeof(Datum));
855         nulls1 = (bool *) palloc(ncolumns1 * sizeof(bool));
856         heap_deform_tuple(&tuple1, tupdesc1, values1, nulls1);
857         values2 = (Datum *) palloc(ncolumns2 * sizeof(Datum));
858         nulls2 = (bool *) palloc(ncolumns2 * sizeof(bool));
859         heap_deform_tuple(&tuple2, tupdesc2, values2, nulls2);
860
861         /*
862          * Scan corresponding columns, allowing for dropped columns in different
863          * places in the two rows.      i1 and i2 are physical column indexes, j is
864          * the logical column index.
865          */
866         i1 = i2 = j = 0;
867         while (i1 < ncolumns1 || i2 < ncolumns2)
868         {
869                 TypeCacheEntry *typentry;
870                 Oid                     collation;
871                 FunctionCallInfoData locfcinfo;
872                 int32           cmpresult;
873
874                 /*
875                  * Skip dropped columns
876                  */
877                 if (i1 < ncolumns1 && tupdesc1->attrs[i1]->attisdropped)
878                 {
879                         i1++;
880                         continue;
881                 }
882                 if (i2 < ncolumns2 && tupdesc2->attrs[i2]->attisdropped)
883                 {
884                         i2++;
885                         continue;
886                 }
887                 if (i1 >= ncolumns1 || i2 >= ncolumns2)
888                         break;                          /* we'll deal with mismatch below loop */
889
890                 /*
891                  * Have two matching columns, they must be same type
892                  */
893                 if (tupdesc1->attrs[i1]->atttypid !=
894                         tupdesc2->attrs[i2]->atttypid)
895                         ereport(ERROR,
896                                         (errcode(ERRCODE_DATATYPE_MISMATCH),
897                                          errmsg("cannot compare dissimilar column types %s and %s at record column %d",
898                                                         format_type_be(tupdesc1->attrs[i1]->atttypid),
899                                                         format_type_be(tupdesc2->attrs[i2]->atttypid),
900                                                         j + 1)));
901
902                 /*
903                  * If they're not same collation, we don't complain here, but the
904                  * comparison function might.
905                  */
906                 collation = tupdesc1->attrs[i1]->attcollation;
907                 if (collation != tupdesc2->attrs[i2]->attcollation)
908                         collation = InvalidOid;
909
910                 /*
911                  * Lookup the comparison function if not done already
912                  */
913                 typentry = my_extra->columns[j].typentry;
914                 if (typentry == NULL ||
915                         typentry->type_id != tupdesc1->attrs[i1]->atttypid)
916                 {
917                         typentry = lookup_type_cache(tupdesc1->attrs[i1]->atttypid,
918                                                                                  TYPECACHE_CMP_PROC_FINFO);
919                         if (!OidIsValid(typentry->cmp_proc_finfo.fn_oid))
920                                 ereport(ERROR,
921                                                 (errcode(ERRCODE_UNDEFINED_FUNCTION),
922                                 errmsg("could not identify a comparison function for type %s",
923                                            format_type_be(typentry->type_id))));
924                         my_extra->columns[j].typentry = typentry;
925                 }
926
927                 /*
928                  * We consider two NULLs equal; NULL > not-NULL.
929                  */
930                 if (!nulls1[i1] || !nulls2[i2])
931                 {
932                         if (nulls1[i1])
933                         {
934                                 /* arg1 is greater than arg2 */
935                                 result = 1;
936                                 break;
937                         }
938                         if (nulls2[i2])
939                         {
940                                 /* arg1 is less than arg2 */
941                                 result = -1;
942                                 break;
943                         }
944
945                         /* Compare the pair of elements */
946                         InitFunctionCallInfoData(locfcinfo, &typentry->cmp_proc_finfo, 2,
947                                                                          collation, NULL, NULL);
948                         locfcinfo.arg[0] = values1[i1];
949                         locfcinfo.arg[1] = values2[i2];
950                         locfcinfo.argnull[0] = false;
951                         locfcinfo.argnull[1] = false;
952                         locfcinfo.isnull = false;
953                         cmpresult = DatumGetInt32(FunctionCallInvoke(&locfcinfo));
954
955                         if (cmpresult < 0)
956                         {
957                                 /* arg1 is less than arg2 */
958                                 result = -1;
959                                 break;
960                         }
961                         else if (cmpresult > 0)
962                         {
963                                 /* arg1 is greater than arg2 */
964                                 result = 1;
965                                 break;
966                         }
967                 }
968
969                 /* equal, so continue to next column */
970                 i1++, i2++, j++;
971         }
972
973         /*
974          * If we didn't break out of the loop early, check for column count
975          * mismatch.  (We do not report such mismatch if we found unequal column
976          * values; is that a feature or a bug?)
977          */
978         if (result == 0)
979         {
980                 if (i1 != ncolumns1 || i2 != ncolumns2)
981                         ereport(ERROR,
982                                         (errcode(ERRCODE_DATATYPE_MISMATCH),
983                                          errmsg("cannot compare record types with different numbers of columns")));
984         }
985
986         pfree(values1);
987         pfree(nulls1);
988         pfree(values2);
989         pfree(nulls2);
990         ReleaseTupleDesc(tupdesc1);
991         ReleaseTupleDesc(tupdesc2);
992
993         /* Avoid leaking memory when handed toasted input. */
994         PG_FREE_IF_COPY(record1, 0);
995         PG_FREE_IF_COPY(record2, 1);
996
997         return result;
998 }
999
1000 /*
1001  * record_eq :
1002  *                compares two records for equality
1003  * result :
1004  *                returns true if the records are equal, false otherwise.
1005  *
1006  * Note: we do not use record_cmp here, since equality may be meaningful in
1007  * datatypes that don't have a total ordering (and hence no btree support).
1008  */
1009 Datum
1010 record_eq(PG_FUNCTION_ARGS)
1011 {
1012         HeapTupleHeader record1 = PG_GETARG_HEAPTUPLEHEADER(0);
1013         HeapTupleHeader record2 = PG_GETARG_HEAPTUPLEHEADER(1);
1014         bool            result = true;
1015         Oid                     tupType1;
1016         Oid                     tupType2;
1017         int32           tupTypmod1;
1018         int32           tupTypmod2;
1019         TupleDesc       tupdesc1;
1020         TupleDesc       tupdesc2;
1021         HeapTupleData tuple1;
1022         HeapTupleData tuple2;
1023         int                     ncolumns1;
1024         int                     ncolumns2;
1025         RecordCompareData *my_extra;
1026         int                     ncols;
1027         Datum      *values1;
1028         Datum      *values2;
1029         bool       *nulls1;
1030         bool       *nulls2;
1031         int                     i1;
1032         int                     i2;
1033         int                     j;
1034
1035         /* Extract type info from the tuples */
1036         tupType1 = HeapTupleHeaderGetTypeId(record1);
1037         tupTypmod1 = HeapTupleHeaderGetTypMod(record1);
1038         tupdesc1 = lookup_rowtype_tupdesc(tupType1, tupTypmod1);
1039         ncolumns1 = tupdesc1->natts;
1040         tupType2 = HeapTupleHeaderGetTypeId(record2);
1041         tupTypmod2 = HeapTupleHeaderGetTypMod(record2);
1042         tupdesc2 = lookup_rowtype_tupdesc(tupType2, tupTypmod2);
1043         ncolumns2 = tupdesc2->natts;
1044
1045         /* Build temporary HeapTuple control structures */
1046         tuple1.t_len = HeapTupleHeaderGetDatumLength(record1);
1047         ItemPointerSetInvalid(&(tuple1.t_self));
1048         tuple1.t_tableOid = InvalidOid;
1049         tuple1.t_data = record1;
1050         tuple2.t_len = HeapTupleHeaderGetDatumLength(record2);
1051         ItemPointerSetInvalid(&(tuple2.t_self));
1052         tuple2.t_tableOid = InvalidOid;
1053         tuple2.t_data = record2;
1054
1055         /*
1056          * We arrange to look up the needed comparison info just once per series
1057          * of calls, assuming the record types don't change underneath us.
1058          */
1059         ncols = Max(ncolumns1, ncolumns2);
1060         my_extra = (RecordCompareData *) fcinfo->flinfo->fn_extra;
1061         if (my_extra == NULL ||
1062                 my_extra->ncolumns < ncols)
1063         {
1064                 fcinfo->flinfo->fn_extra =
1065                         MemoryContextAlloc(fcinfo->flinfo->fn_mcxt,
1066                                                 sizeof(RecordCompareData) - sizeof(ColumnCompareData)
1067                                                            + ncols * sizeof(ColumnCompareData));
1068                 my_extra = (RecordCompareData *) fcinfo->flinfo->fn_extra;
1069                 my_extra->ncolumns = ncols;
1070                 my_extra->record1_type = InvalidOid;
1071                 my_extra->record1_typmod = 0;
1072                 my_extra->record2_type = InvalidOid;
1073                 my_extra->record2_typmod = 0;
1074         }
1075
1076         if (my_extra->record1_type != tupType1 ||
1077                 my_extra->record1_typmod != tupTypmod1 ||
1078                 my_extra->record2_type != tupType2 ||
1079                 my_extra->record2_typmod != tupTypmod2)
1080         {
1081                 MemSet(my_extra->columns, 0, ncols * sizeof(ColumnCompareData));
1082                 my_extra->record1_type = tupType1;
1083                 my_extra->record1_typmod = tupTypmod1;
1084                 my_extra->record2_type = tupType2;
1085                 my_extra->record2_typmod = tupTypmod2;
1086         }
1087
1088         /* Break down the tuples into fields */
1089         values1 = (Datum *) palloc(ncolumns1 * sizeof(Datum));
1090         nulls1 = (bool *) palloc(ncolumns1 * sizeof(bool));
1091         heap_deform_tuple(&tuple1, tupdesc1, values1, nulls1);
1092         values2 = (Datum *) palloc(ncolumns2 * sizeof(Datum));
1093         nulls2 = (bool *) palloc(ncolumns2 * sizeof(bool));
1094         heap_deform_tuple(&tuple2, tupdesc2, values2, nulls2);
1095
1096         /*
1097          * Scan corresponding columns, allowing for dropped columns in different
1098          * places in the two rows.      i1 and i2 are physical column indexes, j is
1099          * the logical column index.
1100          */
1101         i1 = i2 = j = 0;
1102         while (i1 < ncolumns1 || i2 < ncolumns2)
1103         {
1104                 TypeCacheEntry *typentry;
1105                 Oid                     collation;
1106                 FunctionCallInfoData locfcinfo;
1107                 bool            oprresult;
1108
1109                 /*
1110                  * Skip dropped columns
1111                  */
1112                 if (i1 < ncolumns1 && tupdesc1->attrs[i1]->attisdropped)
1113                 {
1114                         i1++;
1115                         continue;
1116                 }
1117                 if (i2 < ncolumns2 && tupdesc2->attrs[i2]->attisdropped)
1118                 {
1119                         i2++;
1120                         continue;
1121                 }
1122                 if (i1 >= ncolumns1 || i2 >= ncolumns2)
1123                         break;                          /* we'll deal with mismatch below loop */
1124
1125                 /*
1126                  * Have two matching columns, they must be same type
1127                  */
1128                 if (tupdesc1->attrs[i1]->atttypid !=
1129                         tupdesc2->attrs[i2]->atttypid)
1130                         ereport(ERROR,
1131                                         (errcode(ERRCODE_DATATYPE_MISMATCH),
1132                                          errmsg("cannot compare dissimilar column types %s and %s at record column %d",
1133                                                         format_type_be(tupdesc1->attrs[i1]->atttypid),
1134                                                         format_type_be(tupdesc2->attrs[i2]->atttypid),
1135                                                         j + 1)));
1136
1137                 /*
1138                  * If they're not same collation, we don't complain here, but the
1139                  * equality function might.
1140                  */
1141                 collation = tupdesc1->attrs[i1]->attcollation;
1142                 if (collation != tupdesc2->attrs[i2]->attcollation)
1143                         collation = InvalidOid;
1144
1145                 /*
1146                  * Lookup the equality function if not done already
1147                  */
1148                 typentry = my_extra->columns[j].typentry;
1149                 if (typentry == NULL ||
1150                         typentry->type_id != tupdesc1->attrs[i1]->atttypid)
1151                 {
1152                         typentry = lookup_type_cache(tupdesc1->attrs[i1]->atttypid,
1153                                                                                  TYPECACHE_EQ_OPR_FINFO);
1154                         if (!OidIsValid(typentry->eq_opr_finfo.fn_oid))
1155                                 ereport(ERROR,
1156                                                 (errcode(ERRCODE_UNDEFINED_FUNCTION),
1157                                 errmsg("could not identify an equality operator for type %s",
1158                                            format_type_be(typentry->type_id))));
1159                         my_extra->columns[j].typentry = typentry;
1160                 }
1161
1162                 /*
1163                  * We consider two NULLs equal; NULL > not-NULL.
1164                  */
1165                 if (!nulls1[i1] || !nulls2[i2])
1166                 {
1167                         if (nulls1[i1] || nulls2[i2])
1168                         {
1169                                 result = false;
1170                                 break;
1171                         }
1172
1173                         /* Compare the pair of elements */
1174                         InitFunctionCallInfoData(locfcinfo, &typentry->eq_opr_finfo, 2,
1175                                                                          collation, NULL, NULL);
1176                         locfcinfo.arg[0] = values1[i1];
1177                         locfcinfo.arg[1] = values2[i2];
1178                         locfcinfo.argnull[0] = false;
1179                         locfcinfo.argnull[1] = false;
1180                         locfcinfo.isnull = false;
1181                         oprresult = DatumGetBool(FunctionCallInvoke(&locfcinfo));
1182                         if (!oprresult)
1183                         {
1184                                 result = false;
1185                                 break;
1186                         }
1187                 }
1188
1189                 /* equal, so continue to next column */
1190                 i1++, i2++, j++;
1191         }
1192
1193         /*
1194          * If we didn't break out of the loop early, check for column count
1195          * mismatch.  (We do not report such mismatch if we found unequal column
1196          * values; is that a feature or a bug?)
1197          */
1198         if (result)
1199         {
1200                 if (i1 != ncolumns1 || i2 != ncolumns2)
1201                         ereport(ERROR,
1202                                         (errcode(ERRCODE_DATATYPE_MISMATCH),
1203                                          errmsg("cannot compare record types with different numbers of columns")));
1204         }
1205
1206         pfree(values1);
1207         pfree(nulls1);
1208         pfree(values2);
1209         pfree(nulls2);
1210         ReleaseTupleDesc(tupdesc1);
1211         ReleaseTupleDesc(tupdesc2);
1212
1213         /* Avoid leaking memory when handed toasted input. */
1214         PG_FREE_IF_COPY(record1, 0);
1215         PG_FREE_IF_COPY(record2, 1);
1216
1217         PG_RETURN_BOOL(result);
1218 }
1219
1220 Datum
1221 record_ne(PG_FUNCTION_ARGS)
1222 {
1223         PG_RETURN_BOOL(!DatumGetBool(record_eq(fcinfo)));
1224 }
1225
1226 Datum
1227 record_lt(PG_FUNCTION_ARGS)
1228 {
1229         PG_RETURN_BOOL(record_cmp(fcinfo) < 0);
1230 }
1231
1232 Datum
1233 record_gt(PG_FUNCTION_ARGS)
1234 {
1235         PG_RETURN_BOOL(record_cmp(fcinfo) > 0);
1236 }
1237
1238 Datum
1239 record_le(PG_FUNCTION_ARGS)
1240 {
1241         PG_RETURN_BOOL(record_cmp(fcinfo) <= 0);
1242 }
1243
1244 Datum
1245 record_ge(PG_FUNCTION_ARGS)
1246 {
1247         PG_RETURN_BOOL(record_cmp(fcinfo) >= 0);
1248 }
1249
1250 Datum
1251 btrecordcmp(PG_FUNCTION_ARGS)
1252 {
1253         PG_RETURN_INT32(record_cmp(fcinfo));
1254 }