Skip to content

Commit a3704d3

Browse files
committed
Preliminary support for composite type I/O; just text for now,
no binary yet.
1 parent c541bb8 commit a3704d3

File tree

1 file changed

+357
-11
lines changed

1 file changed

+357
-11
lines changed

src/backend/utils/adt/rowtypes.c

Lines changed: 357 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -8,14 +8,42 @@
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
/*
@@ -24,12 +52,194 @@
2452
Datum
2553
record_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((unsigned char) *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((unsigned char) *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)
38248
Datum
39249
record_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((unsigned char) 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
/*

0 commit comments

Comments
 (0)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy