| /*------------------------------------------------------------------------- |
| * |
| * rowtypes.c |
| * I/O and comparison functions for generic composite types. |
| * |
| * Portions Copyright (c) 1996-2009, PostgreSQL Global Development Group |
| * Portions Copyright (c) 1994, Regents of the University of California |
| * |
| * |
| * IDENTIFICATION |
| * $PostgreSQL: pgsql/src/backend/utils/adt/rowtypes.c,v 1.17 2006/07/14 14:52:24 momjian Exp $ |
| * |
| *------------------------------------------------------------------------- |
| */ |
| #include "postgres.h" |
| |
| #include <ctype.h> |
| |
| #include "access/heapam.h" |
| #include "catalog/pg_type.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; |
| |
| |
| /* |
| * record_in - input routine for any composite type. |
| */ |
| Datum |
| record_in(PG_FUNCTION_ARGS) |
| { |
| char *string = PG_GETARG_CSTRING(0); |
| Oid tupType = PG_GETARG_OID(1); |
| |
| #ifdef NOT_USED |
| int32 typmod = PG_GETARG_INT32(2); |
| #endif |
| HeapTupleHeader result; |
| int32 tupTypmod; |
| TupleDesc tupdesc; |
| HeapTuple tuple; |
| RecordIOData *my_extra; |
| bool needComma = false; |
| int ncolumns; |
| int i; |
| char *ptr; |
| Datum *values; |
| bool *nulls; |
| StringInfoData buf; |
| |
| /* |
| * 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 = (bool *) palloc(ncolumns * sizeof(bool)); |
| |
| /* |
| * 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 */ |
| while (*ptr && isspace((unsigned char) *ptr)) |
| ptr++; |
| if (*ptr++ != '(') |
| { |
| ReleaseTupleDesc(tupdesc); |
| 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]; |
| Oid column_type = tupdesc->attrs[i]->atttypid; |
| char *column_data; |
| |
| /* Ignore dropped columns in datatype, but fill with nulls */ |
| if (tupdesc->attrs[i]->attisdropped) |
| { |
| values[i] = (Datum) 0; |
| nulls[i] = true; |
| continue; |
| } |
| |
| if (needComma) |
| { |
| /* Skip comma that separates prior field from this one */ |
| if (*ptr == ',') |
| ptr++; |
| else |
| { |
| ReleaseTupleDesc(tupdesc); |
| /* *ptr must be ')' */ |
| ereport(ERROR, |
| (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION), |
| errmsg("malformed record literal: \"%s\"", string), |
| errdetail("Too few columns."), |
| errOmitLocation(true))); |
| } |
| } |
| |
| /* Check for null: completely empty input means null */ |
| if (*ptr == ',' || *ptr == ')') |
| { |
| column_data = NULL; |
| nulls[i] = true; |
| } |
| 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') |
| { |
| ReleaseTupleDesc(tupdesc); |
| ereport(ERROR, |
| (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION), |
| errmsg("malformed record literal: \"%s\"", |
| string), |
| errdetail("Unexpected end of input."))); |
| } |
| if (ch == '\\') |
| { |
| if (*ptr == '\0') |
| { |
| ReleaseTupleDesc(tupdesc); |
| 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); |
| } |
| |
| column_data = buf.data; |
| nulls[i] = false; |
| } |
| |
| /* |
| * Convert the column value |
| */ |
| if (column_info->column_type != column_type) |
| { |
| 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 = column_type; |
| } |
| |
| values[i] = InputFunctionCall(&column_info->proc, |
| column_data, |
| column_info->typioparam, |
| tupdesc->attrs[i]->atttypmod); |
| |
| /* |
| * Prep for next column |
| */ |
| needComma = true; |
| } |
| |
| if (*ptr++ != ')') |
| { |
| ReleaseTupleDesc(tupdesc); |
| ereport(ERROR, |
| (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION), |
| errmsg("malformed record literal: \"%s\"", string), |
| errdetail("Too many columns."), |
| errOmitLocation(true))); |
| } |
| /* Allow trailing whitespace */ |
| while (*ptr && isspace((unsigned char) *ptr)) |
| ptr++; |
| if (*ptr) |
| { |
| ReleaseTupleDesc(tupdesc); |
| ereport(ERROR, |
| (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION), |
| errmsg("malformed record literal: \"%s\"", string), |
| errdetail("Junk after right parenthesis."), |
| errOmitLocation(true))); |
| } |
| tuple = heap_form_tuple(tupdesc, values, nulls); |
| |
| /* |
| * We cannot return tuple->t_data because heap_form_tuple allocates it as |
| * part of a larger chunk, and our caller may expect to be able to pfree |
| * our result. So must copy the info into a new palloc chunk. |
| */ |
| result = (HeapTupleHeader) palloc(tuple->t_len); |
| memcpy(result, tuple->t_data, tuple->t_len); |
| |
| heap_freetuple(tuple); |
| pfree(buf.data); |
| pfree(values); |
| pfree(nulls); |
| ReleaseTupleDesc(tupdesc); |
| |
| PG_RETURN_HEAPTUPLEHEADER(result); |
| } |
| |
| /* |
| * record_out - output routine for any composite type. |
| */ |
| Datum |
| record_out(PG_FUNCTION_ARGS) |
| { |
| HeapTupleHeader rec = PG_GETARG_HEAPTUPLEHEADER(0); |
| Oid tupType; |
| int32 tupTypmod; |
| TupleDesc tupdesc; |
| HeapTupleData tuple; |
| RecordIOData *my_extra; |
| bool needComma = false; |
| int ncolumns; |
| int i; |
| Datum *values; |
| bool *nulls; |
| StringInfoData buf; |
| |
| /* Extract type info from the tuple itself */ |
| tupType = HeapTupleHeaderGetTypeId(rec); |
| tupTypmod = HeapTupleHeaderGetTypMod(rec); |
| 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_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 = (bool *) palloc(ncolumns * sizeof(bool)); |
| |
| /* Break down the tuple into fields */ |
| heap_deform_tuple(&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]; |
| Oid column_type = tupdesc->attrs[i]->atttypid; |
| char *value; |
| char *tmp; |
| bool nq; |
| |
| /* Ignore dropped columns in datatype */ |
| if (tupdesc->attrs[i]->attisdropped) |
| continue; |
| |
| if (needComma) |
| appendStringInfoChar(&buf, ','); |
| needComma = true; |
| |
| if (nulls[i]) |
| { |
| /* emit nothing... */ |
| continue; |
| } |
| |
| /* |
| * Convert the column value to text |
| */ |
| if (column_info->column_type != column_type) |
| { |
| bool typIsVarlena; |
| |
| getTypeOutputInfo(column_type, |
| &column_info->typiofunc, |
| &typIsVarlena); |
| fmgr_info_cxt(column_info->typiofunc, &column_info->proc, |
| fcinfo->flinfo->fn_mcxt); |
| column_info->column_type = column_type; |
| } |
| |
| value = OutputFunctionCall(&column_info->proc, values[i]); |
| |
| /* 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; |
| } |
| } |
| |
| /* And emit the string */ |
| if (nq) |
| appendStringInfoChar(&buf, '"'); |
| for (tmp = value; *tmp; tmp++) |
| { |
| char ch = *tmp; |
| |
| if (ch == '"' || ch == '\\') |
| appendStringInfoChar(&buf, ch); |
| appendStringInfoChar(&buf, ch); |
| } |
| if (nq) |
| appendStringInfoChar(&buf, '"'); |
| } |
| |
| appendStringInfoChar(&buf, ')'); |
| |
| pfree(values); |
| pfree(nulls); |
| ReleaseTupleDesc(tupdesc); |
| |
| PG_RETURN_CSTRING(buf.data); |
| } |
| |
| /* |
| * record_recv - binary input routine for any composite type. |
| */ |
| Datum |
| record_recv(PG_FUNCTION_ARGS) |
| { |
| StringInfo buf = (StringInfo) PG_GETARG_POINTER(0); |
| Oid tupType = PG_GETARG_OID(1); |
| |
| #ifdef NOT_USED |
| int32 typmod = PG_GETARG_INT32(2); |
| #endif |
| HeapTupleHeader result; |
| int32 tupTypmod; |
| TupleDesc tupdesc; |
| HeapTuple tuple; |
| RecordIOData *my_extra; |
| int ncolumns; |
| int usercols; |
| int validcols; |
| int i; |
| Datum *values; |
| bool *nulls; |
| |
| /* |
| * 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 = (bool *) palloc(ncolumns * sizeof(bool)); |
| |
| /* Fetch number of columns user thinks it has */ |
| usercols = pq_getmsgint(buf, 4); |
| |
| /* Need to scan to count nondeleted columns */ |
| validcols = 0; |
| for (i = 0; i < ncolumns; i++) |
| { |
| if (!tupdesc->attrs[i]->attisdropped) |
| validcols++; |
| } |
| if (usercols != validcols) |
| ereport(ERROR, |
| (errcode(ERRCODE_DATATYPE_MISMATCH), |
| errmsg("wrong number of columns: %d, expected %d", |
| usercols, validcols))); |
| |
| /* 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; |
| StringInfoData item_buf; |
| StringInfo bufptr; |
| char csave; |
| |
| /* Ignore dropped columns in datatype, but fill with nulls */ |
| if (tupdesc->attrs[i]->attisdropped) |
| { |
| values[i] = (Datum) 0; |
| nulls[i] = true; |
| continue; |
| } |
| |
| /* 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 */ |
| bufptr = NULL; |
| nulls[i] = true; |
| csave = 0; /* keep compiler quiet */ |
| } |
| 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. |
| */ |
| 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'; |
| |
| bufptr = &item_buf; |
| nulls[i] = false; |
| } |
| |
| /* 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] = ReceiveFunctionCall(&column_info->proc, |
| bufptr, |
| column_info->typioparam, |
| tupdesc->attrs[i]->atttypmod); |
| |
| if (bufptr) |
| { |
| /* 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_form_tuple(tupdesc, values, nulls); |
| |
| /* |
| * We cannot return tuple->t_data because heap_form_tuple allocates it as |
| * part of a larger chunk, and our caller may expect to be able to pfree |
| * our result. So must copy the info into a new palloc chunk. |
| */ |
| result = (HeapTupleHeader) palloc(tuple->t_len); |
| memcpy(result, tuple->t_data, tuple->t_len); |
| |
| heap_freetuple(tuple); |
| pfree(values); |
| pfree(nulls); |
| ReleaseTupleDesc(tupdesc); |
| |
| PG_RETURN_HEAPTUPLEHEADER(result); |
| } |
| |
| /* |
| * record_send - binary output routine for any composite type. |
| */ |
| Datum |
| record_send(PG_FUNCTION_ARGS) |
| { |
| HeapTupleHeader rec = PG_GETARG_HEAPTUPLEHEADER(0); |
| Oid tupType; |
| int32 tupTypmod; |
| TupleDesc tupdesc; |
| HeapTupleData tuple; |
| RecordIOData *my_extra; |
| int ncolumns; |
| int validcols; |
| int i; |
| Datum *values; |
| bool *nulls; |
| StringInfoData buf; |
| |
| /* Extract type info from the tuple itself */ |
| tupType = HeapTupleHeaderGetTypeId(rec); |
| tupTypmod = HeapTupleHeaderGetTypMod(rec); |
| 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_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 = (bool *) palloc(ncolumns * sizeof(bool)); |
| |
| /* Break down the tuple into fields */ |
| heap_deform_tuple(&tuple, tupdesc, values, nulls); |
| |
| /* And build the result string */ |
| pq_begintypsend(&buf); |
| |
| /* Need to scan to count nondeleted columns */ |
| validcols = 0; |
| for (i = 0; i < ncolumns; i++) |
| { |
| if (!tupdesc->attrs[i]->attisdropped) |
| validcols++; |
| } |
| pq_sendint(&buf, validcols, 4); |
| |
| for (i = 0; i < ncolumns; i++) |
| { |
| ColumnIOData *column_info = &my_extra->columns[i]; |
| Oid column_type = tupdesc->attrs[i]->atttypid; |
| bytea *outputbytes; |
| |
| /* Ignore dropped columns in datatype */ |
| if (tupdesc->attrs[i]->attisdropped) |
| continue; |
| |
| pq_sendint(&buf, column_type, sizeof(Oid)); |
| |
| if (nulls[i]) |
| { |
| /* 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, |
| &typIsVarlena); |
| fmgr_info_cxt(column_info->typiofunc, &column_info->proc, |
| fcinfo->flinfo->fn_mcxt); |
| column_info->column_type = column_type; |
| } |
| |
| outputbytes = SendFunctionCall(&column_info->proc, values[i]); |
| |
| /* 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); |
| ReleaseTupleDesc(tupdesc); |
| |
| PG_RETURN_BYTEA_P(pq_endtypsend(&buf)); |
| } |