mirror of
https://github.com/postgres/postgres.git
synced 2025-05-11 05:41:32 +03:00
Preliminary support for composite type I/O; just text for now,
no binary yet.
This commit is contained in:
parent
c541bb86e9
commit
a3704d3dec
@ -8,14 +8,42 @@
|
|||||||
*
|
*
|
||||||
*
|
*
|
||||||
* IDENTIFICATION
|
* 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 "postgres.h"
|
||||||
|
|
||||||
|
#include <ctype.h>
|
||||||
|
|
||||||
|
#include "access/heapam.h"
|
||||||
|
#include "access/htup.h"
|
||||||
|
#include "catalog/pg_type.h"
|
||||||
|
#include "lib/stringinfo.h"
|
||||||
#include "libpq/pqformat.h"
|
#include "libpq/pqformat.h"
|
||||||
#include "utils/builtins.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;
|
||||||
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@ -24,12 +52,194 @@
|
|||||||
Datum
|
Datum
|
||||||
record_in(PG_FUNCTION_ARGS)
|
record_in(PG_FUNCTION_ARGS)
|
||||||
{
|
{
|
||||||
/* Need to decide on external format before we can write this */
|
char *string = PG_GETARG_CSTRING(0);
|
||||||
ereport(ERROR,
|
Oid tupType = PG_GETARG_OID(1);
|
||||||
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
|
HeapTuple tuple;
|
||||||
errmsg("input of composite types not implemented yet")));
|
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
|
Datum
|
||||||
record_out(PG_FUNCTION_ARGS)
|
record_out(PG_FUNCTION_ARGS)
|
||||||
{
|
{
|
||||||
/* Need to decide on external format before we can write this */
|
HeapTupleHeader rec = PG_GETARG_HEAPTUPLEHEADER(0);
|
||||||
ereport(ERROR,
|
Oid tupType = PG_GETARG_OID(1);
|
||||||
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
|
int32 tupTypmod;
|
||||||
errmsg("output of composite types not implemented yet")));
|
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);
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
Loading…
x
Reference in New Issue
Block a user