88 *
99 *
1010 * IDENTIFICATION
11- * $PostgreSQL: pgsql/src/backend/utils/adt/rowtypes.c,v 1.1 2004/04/01 21:28:45 tgl Exp $
11+ * $PostgreSQL: pgsql/src/backend/utils/adt/rowtypes.c,v 1.2 2004/06/06 04:50:28 tgl Exp $
1212 *
1313 *-------------------------------------------------------------------------
1414 */
1515#include "postgres.h"
1616
17+ #include <ctype.h>
18+
19+ #include "access/heapam.h"
20+ #include "access/htup.h"
21+ #include "catalog/pg_type.h"
22+ #include "lib/stringinfo.h"
1723#include "libpq/pqformat.h"
1824#include "utils/builtins.h"
25+ #include "utils/lsyscache.h"
26+ #include "utils/typcache.h"
27+
28+
29+ /*
30+ * structure to cache metadata needed for record I/O
31+ */
32+ typedef struct ColumnIOData
33+ {
34+ Oid column_type ;
35+ Oid typiofunc ;
36+ Oid typioparam ;
37+ FmgrInfo proc ;
38+ }ColumnIOData ;
39+
40+ typedef struct RecordIOData
41+ {
42+ Oid record_type ;
43+ int32 record_typmod ;
44+ int ncolumns ;
45+ ColumnIOData columns [1 ];/* VARIABLE LENGTH ARRAY */
46+ }RecordIOData ;
1947
2048
2149/*
2452Datum
2553record_in (PG_FUNCTION_ARGS )
2654{
27- /* Need to decide on external format before we can write this */
28- ereport (ERROR ,
29- (errcode (ERRCODE_FEATURE_NOT_SUPPORTED ),
30- errmsg ("input of composite types not implemented yet" )));
55+ char * string = PG_GETARG_CSTRING (0 );
56+ Oid tupType = PG_GETARG_OID (1 );
57+ HeapTuple tuple ;
58+ TupleDesc tupdesc ;
59+ RecordIOData * my_extra ;
60+ int ncolumns ;
61+ int i ;
62+ char * ptr ;
63+ Datum * values ;
64+ char * nulls ;
65+ StringInfoData buf ;
3166
32- PG_RETURN_VOID ();/* keep compiler quiet */
67+ /*
68+ * Use the passed type unless it's RECORD; we can't support input
69+ * of anonymous types, mainly because there's no good way to figure
70+ * out which anonymous type is wanted. Note that for RECORD,
71+ * what we'll probably actually get is RECORD's typelem, ie, zero.
72+ */
73+ if (tupType == InvalidOid || tupType == RECORDOID )
74+ ereport (ERROR ,
75+ (errcode (ERRCODE_FEATURE_NOT_SUPPORTED ),
76+ errmsg ("input of anonymous composite types is not implemented" )));
77+ tupdesc = lookup_rowtype_tupdesc (tupType ,-1 );
78+ ncolumns = tupdesc -> natts ;
79+
80+ /*
81+ * We arrange to look up the needed I/O info just once per series of
82+ * calls, assuming the record type doesn't change underneath us.
83+ */
84+ my_extra = (RecordIOData * )fcinfo -> flinfo -> fn_extra ;
85+ if (my_extra == NULL ||
86+ my_extra -> ncolumns != ncolumns )
87+ {
88+ fcinfo -> flinfo -> fn_extra =
89+ MemoryContextAlloc (fcinfo -> flinfo -> fn_mcxt ,
90+ sizeof (RecordIOData )- sizeof (ColumnIOData )
91+ + ncolumns * sizeof (ColumnIOData ));
92+ my_extra = (RecordIOData * )fcinfo -> flinfo -> fn_extra ;
93+ my_extra -> record_type = InvalidOid ;
94+ my_extra -> record_typmod = -1 ;
95+ }
96+
97+ if (my_extra -> record_type != tupType ||
98+ my_extra -> record_typmod != -1 )
99+ {
100+ MemSet (my_extra ,0 ,
101+ sizeof (RecordIOData )- sizeof (ColumnIOData )
102+ + ncolumns * sizeof (ColumnIOData ));
103+ my_extra -> record_type = tupType ;
104+ my_extra -> record_typmod = -1 ;
105+ my_extra -> ncolumns = ncolumns ;
106+ }
107+
108+ values = (Datum * )palloc (ncolumns * sizeof (Datum ));
109+ nulls = (char * )palloc (ncolumns * sizeof (char ));
110+
111+ /*
112+ * Scan the string.
113+ */
114+ ptr = string ;
115+ /* Allow leading whitespace */
116+ while (* ptr && isspace ((unsignedchar )* ptr ))
117+ ptr ++ ;
118+ if (* ptr ++ != '(' )
119+ ereport (ERROR ,
120+ (errcode (ERRCODE_INVALID_TEXT_REPRESENTATION ),
121+ errmsg ("malformed record literal: \"%s\"" ,string ),
122+ errdetail ("Missing left parenthesis." )));
123+
124+ initStringInfo (& buf );
125+
126+ for (i = 0 ;i < ncolumns ;i ++ )
127+ {
128+ ColumnIOData * column_info = & my_extra -> columns [i ];
129+
130+ /* Check for null */
131+ if (* ptr == ',' || * ptr == ')' )
132+ {
133+ values [i ]= (Datum )0 ;
134+ nulls [i ]= 'n' ;
135+ }
136+ else
137+ {
138+ /* Extract string for this column */
139+ bool inquote = false;
140+
141+ buf .len = 0 ;
142+ buf .data [0 ]= '\0' ;
143+ while (inquote || !(* ptr == ',' || * ptr == ')' ))
144+ {
145+ char ch = * ptr ++ ;
146+
147+ if (ch == '\0' )
148+ ereport (ERROR ,
149+ (errcode (ERRCODE_INVALID_TEXT_REPRESENTATION ),
150+ errmsg ("malformed record literal: \"%s\"" ,
151+ string ),
152+ errdetail ("Unexpected end of input." )));
153+ if (ch == '\\' )
154+ {
155+ if (* ptr == '\0' )
156+ ereport (ERROR ,
157+ (errcode (ERRCODE_INVALID_TEXT_REPRESENTATION ),
158+ errmsg ("malformed record literal: \"%s\"" ,
159+ string ),
160+ errdetail ("Unexpected end of input." )));
161+ appendStringInfoChar (& buf ,* ptr ++ );
162+ }
163+ else if (ch == '\"' )
164+ {
165+ if (!inquote )
166+ inquote = true;
167+ else if (* ptr == '\"' )
168+ {
169+ /* doubled quote within quote sequence */
170+ appendStringInfoChar (& buf ,* ptr ++ );
171+ }
172+ else
173+ inquote = false;
174+ }
175+ else
176+ appendStringInfoChar (& buf ,ch );
177+ }
178+
179+ /*
180+ * Convert the column value
181+ */
182+ if (column_info -> column_type != tupdesc -> attrs [i ]-> atttypid )
183+ {
184+ getTypeInputInfo (tupdesc -> attrs [i ]-> atttypid ,
185+ & column_info -> typiofunc ,
186+ & column_info -> typioparam );
187+ fmgr_info_cxt (column_info -> typiofunc ,& column_info -> proc ,
188+ fcinfo -> flinfo -> fn_mcxt );
189+ column_info -> column_type = tupdesc -> attrs [i ]-> atttypid ;
190+ }
191+
192+ values [i ]= FunctionCall3 (& column_info -> proc ,
193+ CStringGetDatum (buf .data ),
194+ ObjectIdGetDatum (column_info -> typioparam ),
195+ Int32GetDatum (tupdesc -> attrs [i ]-> atttypmod ));
196+ nulls [i ]= ' ' ;
197+ }
198+
199+ /*
200+ * Prep for next column
201+ */
202+ if (* ptr == ',' )
203+ {
204+ if (i == ncolumns - 1 )
205+ ereport (ERROR ,
206+ (errcode (ERRCODE_INVALID_TEXT_REPRESENTATION ),
207+ errmsg ("malformed record literal: \"%s\"" ,string ),
208+ errdetail ("Too many columns." )));
209+ ptr ++ ;
210+ }
211+ else
212+ {
213+ /* *ptr must be ')' */
214+ if (i < ncolumns - 1 )
215+ ereport (ERROR ,
216+ (errcode (ERRCODE_INVALID_TEXT_REPRESENTATION ),
217+ errmsg ("malformed record literal: \"%s\"" ,string ),
218+ errdetail ("Too few columns." )));
219+ }
220+ }
221+
222+ if (* ptr ++ != ')' )
223+ ereport (ERROR ,
224+ (errcode (ERRCODE_INVALID_TEXT_REPRESENTATION ),
225+ errmsg ("malformed record literal: \"%s\"" ,string ),
226+ errdetail ("Too many columns." )));
227+ /* Allow trailing whitespace */
228+ while (* ptr && isspace ((unsignedchar )* ptr ))
229+ ptr ++ ;
230+ if (* ptr )
231+ ereport (ERROR ,
232+ (errcode (ERRCODE_INVALID_TEXT_REPRESENTATION ),
233+ errmsg ("malformed record literal: \"%s\"" ,string ),
234+ errdetail ("Junk after right parenthesis." )));
235+
236+ tuple = heap_formtuple (tupdesc ,values ,nulls );
237+
238+ pfree (buf .data );
239+ pfree (values );
240+ pfree (nulls );
241+
242+ PG_RETURN_HEAPTUPLEHEADER (tuple -> t_data );
33243}
34244
35245/*
@@ -38,12 +248,148 @@ record_in(PG_FUNCTION_ARGS)
38248Datum
39249record_out (PG_FUNCTION_ARGS )
40250{
41- /* Need to decide on external format before we can write this */
42- ereport (ERROR ,
43- (errcode (ERRCODE_FEATURE_NOT_SUPPORTED ),
44- errmsg ("output of composite types not implemented yet" )));
251+ HeapTupleHeader rec = PG_GETARG_HEAPTUPLEHEADER (0 );
252+ Oid tupType = PG_GETARG_OID (1 );
253+ int32 tupTypmod ;
254+ TupleDesc tupdesc ;
255+ HeapTupleData tuple ;
256+ RecordIOData * my_extra ;
257+ int ncolumns ;
258+ int i ;
259+ Datum * values ;
260+ char * nulls ;
261+ StringInfoData buf ;
45262
46- PG_RETURN_VOID ();/* keep compiler quiet */
263+ /*
264+ * Use the passed type unless it's RECORD; in that case, we'd better
265+ * get the type info out of the datum itself. Note that for RECORD,
266+ * what we'll probably actually get is RECORD's typelem, ie, zero.
267+ */
268+ if (tupType == InvalidOid || tupType == RECORDOID )
269+ {
270+ tupType = HeapTupleHeaderGetTypeId (rec );
271+ tupTypmod = HeapTupleHeaderGetTypMod (rec );
272+ }
273+ else
274+ tupTypmod = -1 ;
275+ tupdesc = lookup_rowtype_tupdesc (tupType ,tupTypmod );
276+ ncolumns = tupdesc -> natts ;
277+ /* Build a temporary HeapTuple control structure */
278+ tuple .t_len = HeapTupleHeaderGetDatumLength (rec );
279+ ItemPointerSetInvalid (& (tuple .t_self ));
280+ tuple .t_tableOid = InvalidOid ;
281+ tuple .t_data = rec ;
282+
283+ /*
284+ * We arrange to look up the needed I/O info just once per series of
285+ * calls, assuming the record type doesn't change underneath us.
286+ */
287+ my_extra = (RecordIOData * )fcinfo -> flinfo -> fn_extra ;
288+ if (my_extra == NULL ||
289+ my_extra -> ncolumns != ncolumns )
290+ {
291+ fcinfo -> flinfo -> fn_extra =
292+ MemoryContextAlloc (fcinfo -> flinfo -> fn_mcxt ,
293+ sizeof (RecordIOData )- sizeof (ColumnIOData )
294+ + ncolumns * sizeof (ColumnIOData ));
295+ my_extra = (RecordIOData * )fcinfo -> flinfo -> fn_extra ;
296+ my_extra -> record_type = InvalidOid ;
297+ my_extra -> record_typmod = -1 ;
298+ }
299+
300+ if (my_extra -> record_type != tupType ||
301+ my_extra -> record_typmod != tupTypmod )
302+ {
303+ MemSet (my_extra ,0 ,
304+ sizeof (RecordIOData )- sizeof (ColumnIOData )
305+ + ncolumns * sizeof (ColumnIOData ));
306+ my_extra -> record_type = tupType ;
307+ my_extra -> record_typmod = tupTypmod ;
308+ my_extra -> ncolumns = ncolumns ;
309+ }
310+
311+ /* Break down the tuple into fields */
312+ values = (Datum * )palloc (ncolumns * sizeof (Datum ));
313+ nulls = (char * )palloc (ncolumns * sizeof (char ));
314+ heap_deformtuple (& tuple ,tupdesc ,values ,nulls );
315+
316+ /* And build the result string */
317+ initStringInfo (& buf );
318+
319+ appendStringInfoChar (& buf ,'(' );
320+
321+ for (i = 0 ;i < ncolumns ;i ++ )
322+ {
323+ ColumnIOData * column_info = & my_extra -> columns [i ];
324+ char * value ;
325+ char * tmp ;
326+ bool nq ;
327+
328+ if (i > 0 )
329+ appendStringInfoChar (& buf ,',' );
330+
331+ if (nulls [i ]== 'n' )
332+ {
333+ /* emit nothing... */
334+ continue ;
335+ }
336+
337+ /*
338+ * Convert the column value
339+ */
340+ if (column_info -> column_type != tupdesc -> attrs [i ]-> atttypid )
341+ {
342+ bool typIsVarlena ;
343+
344+ getTypeOutputInfo (tupdesc -> attrs [i ]-> atttypid ,
345+ & column_info -> typiofunc ,
346+ & column_info -> typioparam ,
347+ & typIsVarlena );
348+ fmgr_info_cxt (column_info -> typiofunc ,& column_info -> proc ,
349+ fcinfo -> flinfo -> fn_mcxt );
350+ column_info -> column_type = tupdesc -> attrs [i ]-> atttypid ;
351+ }
352+
353+ value = DatumGetCString (FunctionCall3 (& column_info -> proc ,
354+ values [i ],
355+ ObjectIdGetDatum (column_info -> typioparam ),
356+ Int32GetDatum (tupdesc -> attrs [i ]-> atttypmod )));
357+
358+ /* Detect whether we need double quotes for this value */
359+ nq = (value [0 ]== '\0' );/* force quotes for empty string */
360+ for (tmp = value ;* tmp ;tmp ++ )
361+ {
362+ char ch = * tmp ;
363+
364+ if (ch == '"' || ch == '\\' ||
365+ ch == '(' || ch == ')' || ch == ',' ||
366+ isspace ((unsignedchar )ch ))
367+ {
368+ nq = true;
369+ break ;
370+ }
371+ }
372+
373+ if (nq )
374+ appendStringInfoChar (& buf ,'"' );
375+ for (tmp = value ;* tmp ;tmp ++ )
376+ {
377+ char ch = * tmp ;
378+
379+ if (ch == '"' || ch == '\\' )
380+ appendStringInfoChar (& buf ,'\\' );
381+ appendStringInfoChar (& buf ,ch );
382+ }
383+ if (nq )
384+ appendStringInfoChar (& buf ,'"' );
385+ }
386+
387+ appendStringInfoChar (& buf ,')' );
388+
389+ pfree (values );
390+ pfree (nulls );
391+
392+ PG_RETURN_CSTRING (buf .data );
47393}
48394
49395/*