Preliminary support for composite type I/O; just text for now,
authorTom Lane
Sun, 6 Jun 2004 04:50:28 +0000 (04:50 +0000)
committerTom Lane
Sun, 6 Jun 2004 04:50:28 +0000 (04:50 +0000)
no binary yet.

src/backend/utils/adt/rowtypes.c

index b487dfc9047e7afaa54466897af27905d86dc07d..96cbac992c57ff7336220ed69d757846410207b3 100644 (file)
@@ -8,14 +8,42 @@
  *
  *
  * IDENTIFICATION
- *   $PostgreSQL: pgsql/src/backend/utils/adt/rowtypes.c,v 1.1 2004/04/01 21:28:45 tgl Exp $
+ *   $PostgreSQL: pgsql/src/backend/utils/adt/rowtypes.c,v 1.2 2004/06/06 04:50:28 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
 #include "postgres.h"
 
+#include 
+
+#include "access/heapam.h"
+#include "access/htup.h"
+#include "catalog/pg_type.h"
+#include "lib/stringinfo.h"
 #include "libpq/pqformat.h"
 #include "utils/builtins.h"
+#include "utils/lsyscache.h"
+#include "utils/typcache.h"
+
+
+/*
+ * structure to cache metadata needed for record I/O
+ */
+typedef struct ColumnIOData
+{
+   Oid         column_type;
+   Oid         typiofunc;
+   Oid         typioparam;
+   FmgrInfo    proc;
+} ColumnIOData;
+
+typedef struct RecordIOData
+{
+   Oid         record_type;
+   int32       record_typmod;
+   int         ncolumns;
+   ColumnIOData columns[1];    /* VARIABLE LENGTH ARRAY */
+} RecordIOData;
 
 
 /*
 Datum
 record_in(PG_FUNCTION_ARGS)
 {
-   /* Need to decide on external format before we can write this */
-   ereport(ERROR,
-           (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-            errmsg("input of composite types not implemented yet")));
+   char       *string = PG_GETARG_CSTRING(0);
+   Oid         tupType = PG_GETARG_OID(1);
+   HeapTuple   tuple;
+   TupleDesc   tupdesc;
+   RecordIOData *my_extra;
+   int         ncolumns;
+   int         i;
+   char       *ptr;
+   Datum      *values;
+   char       *nulls;
+   StringInfoData buf;
 
-   PG_RETURN_VOID();           /* keep compiler quiet */
+   /*
+    * Use the passed type unless it's RECORD; we can't support input
+    * of anonymous types, mainly because there's no good way to figure
+    * out which anonymous type is wanted.  Note that for RECORD,
+    * what we'll probably actually get is RECORD's typelem, ie, zero.
+    */
+   if (tupType == InvalidOid || tupType == RECORDOID)
+       ereport(ERROR,
+               (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+                errmsg("input of anonymous composite types is not implemented")));
+   tupdesc = lookup_rowtype_tupdesc(tupType, -1);
+   ncolumns = tupdesc->natts;
+
+   /*
+    * We arrange to look up the needed I/O info just once per series of
+    * calls, assuming the record type doesn't change underneath us.
+    */
+   my_extra = (RecordIOData *) fcinfo->flinfo->fn_extra;
+   if (my_extra == NULL ||
+       my_extra->ncolumns != ncolumns)
+   {
+       fcinfo->flinfo->fn_extra =
+           MemoryContextAlloc(fcinfo->flinfo->fn_mcxt,
+                              sizeof(RecordIOData) - sizeof(ColumnIOData)
+                              + ncolumns * sizeof(ColumnIOData));
+       my_extra = (RecordIOData *) fcinfo->flinfo->fn_extra;
+       my_extra->record_type = InvalidOid;
+       my_extra->record_typmod = -1;
+   }
+
+   if (my_extra->record_type != tupType ||
+       my_extra->record_typmod != -1)
+   {
+       MemSet(my_extra, 0,
+              sizeof(RecordIOData) - sizeof(ColumnIOData)
+              + ncolumns * sizeof(ColumnIOData));
+       my_extra->record_type = tupType;
+       my_extra->record_typmod = -1;
+       my_extra->ncolumns = ncolumns;
+   }
+
+   values = (Datum *) palloc(ncolumns * sizeof(Datum));
+   nulls = (char *) palloc(ncolumns * sizeof(char));
+
+   /*
+    * Scan the string.
+    */
+   ptr = string;
+   /* Allow leading whitespace */
+   while (*ptr && isspace((unsigned char) *ptr))
+       ptr++;
+   if (*ptr++ != '(')
+       ereport(ERROR,
+               (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
+                errmsg("malformed record literal: \"%s\"", string),
+                errdetail("Missing left parenthesis.")));
+
+   initStringInfo(&buf);
+
+   for (i = 0; i < ncolumns; i++)
+   {
+       ColumnIOData *column_info = &my_extra->columns[i];
+
+       /* Check for null */
+       if (*ptr == ',' || *ptr == ')')
+       {
+           values[i] = (Datum) 0;
+           nulls[i] = 'n';
+       }
+       else
+       {
+           /* Extract string for this column */
+           bool    inquote = false;
+
+           buf.len = 0;
+           buf.data[0] = '\0';
+           while (inquote || !(*ptr == ',' || *ptr == ')'))
+           {
+               char ch = *ptr++;
+
+               if (ch == '\0')
+                   ereport(ERROR,
+                           (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
+                            errmsg("malformed record literal: \"%s\"",
+                                   string),
+                            errdetail("Unexpected end of input.")));
+               if (ch == '\\')
+               {
+                   if (*ptr == '\0')
+                       ereport(ERROR,
+                               (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
+                                errmsg("malformed record literal: \"%s\"",
+                                       string),
+                                errdetail("Unexpected end of input.")));
+                   appendStringInfoChar(&buf, *ptr++);
+               }
+               else if (ch == '\"')
+               {
+                   if (!inquote)
+                       inquote = true;
+                   else if (*ptr == '\"')
+                   {
+                       /* doubled quote within quote sequence */
+                       appendStringInfoChar(&buf, *ptr++);
+                   }
+                   else
+                       inquote = false;
+               }
+               else
+                   appendStringInfoChar(&buf, ch);
+           }
+
+           /*
+            * Convert the column value
+            */
+           if (column_info->column_type != tupdesc->attrs[i]->atttypid)
+           {
+               getTypeInputInfo(tupdesc->attrs[i]->atttypid,
+                                &column_info->typiofunc,
+                                &column_info->typioparam);
+               fmgr_info_cxt(column_info->typiofunc, &column_info->proc,
+                             fcinfo->flinfo->fn_mcxt);
+               column_info->column_type = tupdesc->attrs[i]->atttypid;
+           }
+
+           values[i] = FunctionCall3(&column_info->proc,
+                                     CStringGetDatum(buf.data),
+                                     ObjectIdGetDatum(column_info->typioparam),
+                                     Int32GetDatum(tupdesc->attrs[i]->atttypmod));
+           nulls[i] = ' ';
+       }
+
+       /*
+        * Prep for next column
+        */
+       if (*ptr == ',')
+       {
+           if (i == ncolumns-1)
+               ereport(ERROR,
+                       (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
+                        errmsg("malformed record literal: \"%s\"", string),
+                        errdetail("Too many columns.")));
+           ptr++;
+       }
+       else
+       {
+           /* *ptr must be ')' */
+           if (i < ncolumns-1)
+               ereport(ERROR,
+                       (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
+                        errmsg("malformed record literal: \"%s\"", string),
+                        errdetail("Too few columns.")));
+       }
+   }
+
+   if (*ptr++ != ')')
+       ereport(ERROR,
+               (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
+                errmsg("malformed record literal: \"%s\"", string),
+                errdetail("Too many columns.")));
+   /* Allow trailing whitespace */
+   while (*ptr && isspace((unsigned char) *ptr))
+       ptr++;
+   if (*ptr)
+       ereport(ERROR,
+               (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
+                errmsg("malformed record literal: \"%s\"", string),
+                errdetail("Junk after right parenthesis.")));
+
+   tuple = heap_formtuple(tupdesc, values, nulls);
+
+   pfree(buf.data);
+   pfree(values);
+   pfree(nulls);
+
+   PG_RETURN_HEAPTUPLEHEADER(tuple->t_data);
 }
 
 /*
@@ -38,12 +248,148 @@ record_in(PG_FUNCTION_ARGS)
 Datum
 record_out(PG_FUNCTION_ARGS)
 {
-   /* Need to decide on external format before we can write this */
-   ereport(ERROR,
-           (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-            errmsg("output of composite types not implemented yet")));
+   HeapTupleHeader rec = PG_GETARG_HEAPTUPLEHEADER(0);
+   Oid         tupType = PG_GETARG_OID(1);
+   int32       tupTypmod;
+   TupleDesc   tupdesc;
+   HeapTupleData tuple;
+   RecordIOData *my_extra;
+   int         ncolumns;
+   int         i;
+   Datum      *values;
+   char       *nulls;
+   StringInfoData buf;
 
-   PG_RETURN_VOID();           /* keep compiler quiet */
+   /*
+    * Use the passed type unless it's RECORD; in that case, we'd better
+    * get the type info out of the datum itself.  Note that for RECORD,
+    * what we'll probably actually get is RECORD's typelem, ie, zero.
+    */
+   if (tupType == InvalidOid || tupType == RECORDOID)
+   {
+       tupType = HeapTupleHeaderGetTypeId(rec);
+       tupTypmod = HeapTupleHeaderGetTypMod(rec);
+   }
+   else
+       tupTypmod = -1;
+   tupdesc = lookup_rowtype_tupdesc(tupType, tupTypmod);
+   ncolumns = tupdesc->natts;
+   /* Build a temporary HeapTuple control structure */
+   tuple.t_len = HeapTupleHeaderGetDatumLength(rec);
+   ItemPointerSetInvalid(&(tuple.t_self));
+   tuple.t_tableOid = InvalidOid;
+   tuple.t_data = rec;
+
+   /*
+    * We arrange to look up the needed I/O info just once per series of
+    * calls, assuming the record type doesn't change underneath us.
+    */
+   my_extra = (RecordIOData *) fcinfo->flinfo->fn_extra;
+   if (my_extra == NULL ||
+       my_extra->ncolumns != ncolumns)
+   {
+       fcinfo->flinfo->fn_extra =
+           MemoryContextAlloc(fcinfo->flinfo->fn_mcxt,
+                              sizeof(RecordIOData) - sizeof(ColumnIOData)
+                              + ncolumns * sizeof(ColumnIOData));
+       my_extra = (RecordIOData *) fcinfo->flinfo->fn_extra;
+       my_extra->record_type = InvalidOid;
+       my_extra->record_typmod = -1;
+   }
+
+   if (my_extra->record_type != tupType ||
+       my_extra->record_typmod != tupTypmod)
+   {
+       MemSet(my_extra, 0,
+              sizeof(RecordIOData) - sizeof(ColumnIOData)
+              + ncolumns * sizeof(ColumnIOData));
+       my_extra->record_type = tupType;
+       my_extra->record_typmod = tupTypmod;
+       my_extra->ncolumns = ncolumns;
+   }
+
+   /* Break down the tuple into fields */
+   values = (Datum *) palloc(ncolumns * sizeof(Datum));
+   nulls = (char *) palloc(ncolumns * sizeof(char));
+   heap_deformtuple(&tuple, tupdesc, values, nulls);
+
+   /* And build the result string */
+   initStringInfo(&buf);
+
+   appendStringInfoChar(&buf, '(');
+
+   for (i = 0; i < ncolumns; i++)
+   {
+       ColumnIOData *column_info = &my_extra->columns[i];
+       char    *value;
+       char    *tmp;
+       bool    nq;
+
+       if (i > 0)
+           appendStringInfoChar(&buf, ',');
+
+       if (nulls[i] == 'n')
+       {
+           /* emit nothing... */
+           continue;
+       }
+
+       /*
+        * Convert the column value
+        */
+       if (column_info->column_type != tupdesc->attrs[i]->atttypid)
+       {
+           bool    typIsVarlena;
+
+           getTypeOutputInfo(tupdesc->attrs[i]->atttypid,
+                             &column_info->typiofunc,
+                             &column_info->typioparam,
+                             &typIsVarlena);
+           fmgr_info_cxt(column_info->typiofunc, &column_info->proc,
+                         fcinfo->flinfo->fn_mcxt);
+           column_info->column_type = tupdesc->attrs[i]->atttypid;
+       }
+
+       value = DatumGetCString(FunctionCall3(&column_info->proc,
+                                             values[i],
+                                             ObjectIdGetDatum(column_info->typioparam),
+                                             Int32GetDatum(tupdesc->attrs[i]->atttypmod)));
+
+       /* Detect whether we need double quotes for this value */
+       nq = (value[0] == '\0');    /* force quotes for empty string */
+       for (tmp = value; *tmp; tmp++)
+       {
+           char        ch = *tmp;
+
+           if (ch == '"' || ch == '\\' ||
+               ch == '(' || ch == ')' || ch == ',' ||
+               isspace((unsigned char) ch))
+           {
+               nq = true;
+               break;
+           }
+       }
+
+       if (nq)
+           appendStringInfoChar(&buf, '"');
+       for (tmp = value; *tmp; tmp++)
+       {
+           char        ch = *tmp;
+
+           if (ch == '"' || ch == '\\')
+               appendStringInfoChar(&buf, '\\');
+           appendStringInfoChar(&buf, ch);
+       }
+       if (nq)
+           appendStringInfoChar(&buf, '"');
+   }
+
+   appendStringInfoChar(&buf, ')');
+
+   pfree(values);
+   pfree(nulls);
+
+   PG_RETURN_CSTRING(buf.data);
 }
 
 /*