From 62c3e61e50baacadcb038690353a3abc1f474538 Mon Sep 17 00:00:00 2001 From: Tom Lane <tgl@sss.pgh.pa.us> Date: Sun, 6 Jun 2004 18:06:25 +0000 Subject: [PATCH] Add binary I/O support for composite types. --- src/backend/utils/adt/rowtypes.c | 319 ++++++++++++++++++++++++++++--- 1 file changed, 291 insertions(+), 28 deletions(-) diff --git a/src/backend/utils/adt/rowtypes.c b/src/backend/utils/adt/rowtypes.c index 96cbac992c5..08189d3c143 100644 --- a/src/backend/utils/adt/rowtypes.c +++ b/src/backend/utils/adt/rowtypes.c @@ -8,7 +8,7 @@ * * * IDENTIFICATION - * $PostgreSQL: pgsql/src/backend/utils/adt/rowtypes.c,v 1.2 2004/06/06 04:50:28 tgl Exp $ + * $PostgreSQL: pgsql/src/backend/utils/adt/rowtypes.c,v 1.3 2004/06/06 18:06:25 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -54,8 +54,9 @@ record_in(PG_FUNCTION_ARGS) { char *string = PG_GETARG_CSTRING(0); Oid tupType = PG_GETARG_OID(1); - HeapTuple tuple; + int32 tupTypmod; TupleDesc tupdesc; + HeapTuple tuple; RecordIOData *my_extra; int ncolumns; int i; @@ -74,7 +75,8 @@ record_in(PG_FUNCTION_ARGS) ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), errmsg("input of anonymous composite types is not implemented"))); - tupdesc = lookup_rowtype_tupdesc(tupType, -1); + tupTypmod = -1; /* for all non-anonymous types */ + tupdesc = lookup_rowtype_tupdesc(tupType, tupTypmod); ncolumns = tupdesc->natts; /* @@ -91,17 +93,17 @@ record_in(PG_FUNCTION_ARGS) + ncolumns * sizeof(ColumnIOData)); my_extra = (RecordIOData *) fcinfo->flinfo->fn_extra; my_extra->record_type = InvalidOid; - my_extra->record_typmod = -1; + my_extra->record_typmod = 0; } if (my_extra->record_type != tupType || - my_extra->record_typmod != -1) + 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 = -1; + my_extra->record_typmod = tupTypmod; my_extra->ncolumns = ncolumns; } @@ -109,7 +111,8 @@ record_in(PG_FUNCTION_ARGS) nulls = (char *) palloc(ncolumns * sizeof(char)); /* - * Scan the string. + * Scan the string. We use "buf" to accumulate the de-quoted data + * for each column, which is then fed to the appropriate input converter. */ ptr = string; /* Allow leading whitespace */ @@ -126,8 +129,9 @@ record_in(PG_FUNCTION_ARGS) for (i = 0; i < ncolumns; i++) { ColumnIOData *column_info = &my_extra->columns[i]; + Oid column_type = tupdesc->attrs[i]->atttypid; - /* Check for null */ + /* Check for null: completely empty input means null */ if (*ptr == ',' || *ptr == ')') { values[i] = (Datum) 0; @@ -179,14 +183,14 @@ record_in(PG_FUNCTION_ARGS) /* * Convert the column value */ - if (column_info->column_type != tupdesc->attrs[i]->atttypid) + if (column_info->column_type != column_type) { - getTypeInputInfo(tupdesc->attrs[i]->atttypid, + getTypeInputInfo(column_type, &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; + column_info->column_type = column_type; } values[i] = FunctionCall3(&column_info->proc, @@ -219,6 +223,7 @@ record_in(PG_FUNCTION_ARGS) } } + /* The check for ')' here is redundant except when ncolumns == 0 */ if (*ptr++ != ')') ereport(ERROR, (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION), @@ -274,6 +279,7 @@ record_out(PG_FUNCTION_ARGS) 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)); @@ -294,7 +300,7 @@ record_out(PG_FUNCTION_ARGS) + ncolumns * sizeof(ColumnIOData)); my_extra = (RecordIOData *) fcinfo->flinfo->fn_extra; my_extra->record_type = InvalidOid; - my_extra->record_typmod = -1; + my_extra->record_typmod = 0; } if (my_extra->record_type != tupType || @@ -308,9 +314,10 @@ record_out(PG_FUNCTION_ARGS) my_extra->ncolumns = ncolumns; } - /* Break down the tuple into fields */ values = (Datum *) palloc(ncolumns * sizeof(Datum)); nulls = (char *) palloc(ncolumns * sizeof(char)); + + /* Break down the tuple into fields */ heap_deformtuple(&tuple, tupdesc, values, nulls); /* And build the result string */ @@ -321,6 +328,7 @@ record_out(PG_FUNCTION_ARGS) for (i = 0; i < ncolumns; i++) { ColumnIOData *column_info = &my_extra->columns[i]; + Oid column_type = tupdesc->attrs[i]->atttypid; char *value; char *tmp; bool nq; @@ -335,19 +343,19 @@ record_out(PG_FUNCTION_ARGS) } /* - * Convert the column value + * Convert the column value to text */ - if (column_info->column_type != tupdesc->attrs[i]->atttypid) + if (column_info->column_type != column_type) { bool typIsVarlena; - getTypeOutputInfo(tupdesc->attrs[i]->atttypid, + getTypeOutputInfo(column_type, &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; + column_info->column_type = column_type; } value = DatumGetCString(FunctionCall3(&column_info->proc, @@ -370,6 +378,7 @@ record_out(PG_FUNCTION_ARGS) } } + /* And emit the string */ if (nq) appendStringInfoChar(&buf, '"'); for (tmp = value; *tmp; tmp++) @@ -377,7 +386,7 @@ record_out(PG_FUNCTION_ARGS) char ch = *tmp; if (ch == '"' || ch == '\\') - appendStringInfoChar(&buf, '\\'); + appendStringInfoChar(&buf, ch); appendStringInfoChar(&buf, ch); } if (nq) @@ -398,12 +407,154 @@ record_out(PG_FUNCTION_ARGS) Datum record_recv(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"))); + StringInfo buf = (StringInfo) PG_GETARG_POINTER(0); + Oid tupType = PG_GETARG_OID(1); + int32 tupTypmod; + TupleDesc tupdesc; + HeapTuple tuple; + RecordIOData *my_extra; + int ncolumns; + int i; + Datum *values; + char *nulls; - 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"))); + tupTypmod = -1; /* for all non-anonymous types */ + tupdesc = lookup_rowtype_tupdesc(tupType, tupTypmod); + 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 = 0; + } + + 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; + } + + values = (Datum *) palloc(ncolumns * sizeof(Datum)); + nulls = (char *) palloc(ncolumns * sizeof(char)); + + /* Verify number of columns */ + i = pq_getmsgint(buf, 4); + if (i != ncolumns) + ereport(ERROR, + (errcode(ERRCODE_DATATYPE_MISMATCH), + errmsg("wrong number of columns: %d, expected %d", + i, ncolumns))); + + /* Process each column */ + for (i = 0; i < ncolumns; i++) + { + ColumnIOData *column_info = &my_extra->columns[i]; + Oid column_type = tupdesc->attrs[i]->atttypid; + Oid coltypoid; + int itemlen; + + /* Verify column datatype */ + coltypoid = pq_getmsgint(buf, sizeof(Oid)); + if (coltypoid != column_type) + ereport(ERROR, + (errcode(ERRCODE_DATATYPE_MISMATCH), + errmsg("wrong data type: %u, expected %u", + coltypoid, column_type))); + + /* Get and check the item length */ + itemlen = pq_getmsgint(buf, 4); + if (itemlen < -1 || itemlen > (buf->len - buf->cursor)) + ereport(ERROR, + (errcode(ERRCODE_INVALID_BINARY_REPRESENTATION), + errmsg("insufficient data left in message"))); + + if (itemlen == -1) + { + /* -1 length means NULL */ + values[i] = (Datum) 0; + nulls[i] = 'n'; + } + else + { + /* + * Rather than copying data around, we just set up a phony + * StringInfo pointing to the correct portion of the input buffer. + * We assume we can scribble on the input buffer so as to maintain + * the convention that StringInfos have a trailing null. + */ + StringInfoData item_buf; + char csave; + + item_buf.data = &buf->data[buf->cursor]; + item_buf.maxlen = itemlen + 1; + item_buf.len = itemlen; + item_buf.cursor = 0; + + buf->cursor += itemlen; + + csave = buf->data[buf->cursor]; + buf->data[buf->cursor] = '\0'; + + /* Now call the column's receiveproc */ + if (column_info->column_type != column_type) + { + getTypeBinaryInputInfo(column_type, + &column_info->typiofunc, + &column_info->typioparam); + fmgr_info_cxt(column_info->typiofunc, &column_info->proc, + fcinfo->flinfo->fn_mcxt); + column_info->column_type = column_type; + } + + values[i] = FunctionCall2(&column_info->proc, + PointerGetDatum(&item_buf), + ObjectIdGetDatum(column_info->typioparam)); + + nulls[i] = ' '; + + /* Trouble if it didn't eat the whole buffer */ + if (item_buf.cursor != itemlen) + ereport(ERROR, + (errcode(ERRCODE_INVALID_BINARY_REPRESENTATION), + errmsg("improper binary format in record column %d", + i + 1))); + + buf->data[buf->cursor] = csave; + } + } + + tuple = heap_formtuple(tupdesc, values, nulls); + + pfree(values); + pfree(nulls); + + PG_RETURN_HEAPTUPLEHEADER(tuple->t_data); } /* @@ -412,10 +563,122 @@ record_recv(PG_FUNCTION_ARGS) Datum record_send(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; + + /* + * 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 = 0; + } + + 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; + } + + values = (Datum *) palloc(ncolumns * sizeof(Datum)); + nulls = (char *) palloc(ncolumns * sizeof(char)); + + /* Break down the tuple into fields */ + heap_deformtuple(&tuple, tupdesc, values, nulls); + + /* And build the result string */ + pq_begintypsend(&buf); + + pq_sendint(&buf, ncolumns, 4); + + for (i = 0; i < ncolumns; i++) + { + ColumnIOData *column_info = &my_extra->columns[i]; + Oid column_type = tupdesc->attrs[i]->atttypid; + bytea *outputbytes; + + pq_sendint(&buf, column_type, sizeof(Oid)); + + if (nulls[i] == 'n') + { + /* emit -1 data length to signify a NULL */ + pq_sendint(&buf, -1, 4); + continue; + } + + /* + * Convert the column value to binary + */ + if (column_info->column_type != column_type) + { + bool typIsVarlena; + + getTypeBinaryOutputInfo(column_type, + &column_info->typiofunc, + &column_info->typioparam, + &typIsVarlena); + fmgr_info_cxt(column_info->typiofunc, &column_info->proc, + fcinfo->flinfo->fn_mcxt); + column_info->column_type = column_type; + } + + outputbytes = DatumGetByteaP(FunctionCall2(&column_info->proc, + values[i], + ObjectIdGetDatum(column_info->typioparam))); + + /* We assume the result will not have been toasted */ + pq_sendint(&buf, VARSIZE(outputbytes) - VARHDRSZ, 4); + pq_sendbytes(&buf, VARDATA(outputbytes), + VARSIZE(outputbytes) - VARHDRSZ); + pfree(outputbytes); + } + + pfree(values); + pfree(nulls); - PG_RETURN_VOID(); /* keep compiler quiet */ + PG_RETURN_BYTEA_P(pq_endtypsend(&buf)); } -- GitLab