blob: 986da28185c46b7a3f0235e1a2817a73afc18cc0 [file]
/*-------------------------------------------------------------------------
*
* gpdbwritableformatter.c
*
* This is the GPDB side for serializing and deserializing a tuple to a
* common format which can be parsed/generated by Hadoop's GPDBWritable.
*
* The serialized form produced by gpdbwritableformatter_export is identical to
* GPDBWritable.write(DataOutput out).
*
* The deserialization gpwritableformatter_import can deserialize the
* bytes produced by gpdbwritableformatter_export and GPDBWritable.write.
*
*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*
*-------------------------------------------------------------------------
*/
#include "postgres.h"
#include "fmgr.h"
#include "funcapi.h"
#include "access/formatter.h"
#include "catalog/namespace.h"
#include "catalog/pg_proc.h"
#include "utils/builtins.h"
#include "utils/memutils.h"
#include "utils/typcache.h"
#include "utils/syscache.h"
#include "utils/lsyscache.h"
#include <unistd.h>
#include "access/external.h"
PG_FUNCTION_INFO_V1(gpdbwritableformatter_export);
PG_FUNCTION_INFO_V1(gpdbwritableformatter_import);
Datum gpdbwritableformatter_import(PG_FUNCTION_ARGS);
Datum gpdbwritableformatter_export(PG_FUNCTION_ARGS);
static Form_pg_attribute getAttributeFromTupleDesc(TupleDesc, int);
static const int ERR_COL_OFFSET = 9;
static const int FIRST_LINE_NUM = 1;
typedef struct
{
/* The Datum/null of the tuple */
Datum *values;
bool *nulls;
int lineno;
/* The export formatted value/len */
char **outval;
int *outlen;
int *outpadlen; /* padding length for alignment */
/* Buffer to hold the export formatted tuple */
StringInfo export_format_tuple;
/*
* (Binary) In/Out functions.
*
* For import, it's input function; for export, it's output function. If
* the type is using binary format, the function will the be binary
* conversion function.
*/
FmgrInfo *io_functions;
Oid *typioparams;
} format_t;
/*
* Serialize the object using the following format:
* Total Length | Version | error | #columns | Col type | Col type |... | Null Bit array | Col val...
* 4 byte | 2 byte 1 byte | 2 byte 2 byte 2 byte ceil(#col/8) byte fixed length or var len
*
* For fixed length type, we know the length.
* In the col val, we align pad according to the alignment requirement of the type.
* For var length type, the alignment is always 4 byte.
* For var length type, col val is <4 byte length><payload val>
*/
#define GPDBWRITABLE_VERSION 2
/* for backward compatibility */
#define GPDBWRITABLE_PREV_VERSION 1
#define FORMATTER_ENCODING_ERR_MSG "gpdbwritable formatter can only %s UTF8 formatted data. Define the external table with ENCODING UTF8"
/* Bit flag */
#define GPDBWRITABLE_BITFLAG_ISNULL 1 /* Column is null */
/*
* appendStringInfoFill
*
* Append a single byte, repeated 0 or more times, to str.
*/
static void
appendStringInfoFill(StringInfo str, int occurrences, char ch)
{
/* Length must not overflow. */
if (str->len + occurrences <= str->len)
return;
/* Make more room if needed */
if (str->len + occurrences >= str->maxlen)
enlargeStringInfo(str, occurrences);
/* Fill specified number of bytes with the character. */
memset(str->data + str->len, ch, occurrences);
str->len += occurrences;
str->data[str->len] = '\0';
}
#ifndef unlikely
#if __GNUC__ > 3
#define unlikely(x) __builtin_expect((x) != 0, 0)
#else
#define unlikely(x) ((x) != 0)
#endif
#endif
#define ENSURE_BUF_LEN(buf_len, buf_idx, len_wanted) \
do { \
if (unlikely((buf_len) - (buf_idx) < (len_wanted) || (len_wanted < 0))) { \
ereport(FATAL, (errprintstack(true), \
errcode(ERRCODE_EXTERNAL_ROUTINE_EXCEPTION), \
errmsg("buffer is too small: buf_len=%d, len_wanted=%d", \
(buf_len) - (buf_idx), (len_wanted)))); \
} \
} while (0)
/*
* Write a int4 to the buffer
*/
static void
appendIntToBuffer(StringInfo buf, int val)
{
uint32 n32 = htonl((uint32) val);
appendBinaryStringInfo(buf, (char *) &n32, 4);
}
/*
* Read a int from the buffer, given the offset;
* it will return the int value and increase the offset
*/
static int
readIntFromBuffer(char *buffer, int buf_len, int *offset)
{
uint32 n32;
ENSURE_BUF_LEN(buf_len, *offset, 4);
memcpy(&n32, &buffer[*offset], sizeof(int));
*offset += 4;
return ntohl(n32);
}
/*
* Write a int2 to the buffer
*/
static void
appendInt2ToBuffer(StringInfo buf, uint16 val)
{
uint16 n16 = htons((uint16) val);
appendBinaryStringInfo(buf, (char *) &n16, 2);
}
/*
* Read a int2 from the buffer, given the offset;
* it will return the int value and increase the offset
*/
static uint16
readInt2FromBuffer(char *buffer, int buf_len, int *offset)
{
uint16 n16;
ENSURE_BUF_LEN(buf_len, *offset, 2);
memcpy(&n16, &buffer[*offset], sizeof(uint16));
*offset += 2;
return ntohs(n16);
}
/*
* Write a int1 to the buffer
*/
static void
appendInt1ToBuffer(StringInfo buf, uint8 val)
{
unsigned char n8;
n8 = (unsigned char) val;
appendBinaryStringInfo(buf, (char *) &n8, 1);
}
/*
* Read a int1 from the buffer, given the offset;
* it will return the int value and increase the offset
*/
static uint8
readInt1FromBuffer(char *buffer, int buf_len, int *offset)
{
uint8 n8;
ENSURE_BUF_LEN(buf_len, *offset, 1);
memcpy(&n8, &buffer[*offset], sizeof(uint8));
*offset += 1;
return n8;
}
/*
* Tells whether the given type will be formatted as binary or text
*/
static inline bool
isBinaryFormatType(Oid typeid)
{
/* For version 1, we support binary format for these type */
return (typeid == BOOLOID ||
typeid == BYTEAOID ||
typeid == FLOAT4OID ||
typeid == FLOAT8OID ||
typeid == INT2OID ||
typeid == INT4OID ||
typeid == INT8OID);
}
/*
* Tells whether the given type is variable length
*/
static inline bool
isVariableLength(Oid typeid)
{
return (typeid == BYTEAOID || !isBinaryFormatType(typeid));
}
/*
* Convert typeOID to java enum DBType.ordinal()
*/
static inline int8
getJavaEnumOrdinal(Oid typeid)
{
/* For version 1, we support binary format for these type */
switch (typeid)
{
case INT8OID:
return 0;
case BOOLOID:
return 1;
case FLOAT8OID:
return 2;
case INT4OID:
return 3;
case FLOAT4OID:
return 4;
case INT2OID:
return 5;
case BYTEAOID:
return 6;
}
return 7;
}
/*
* Convert java enum DBType.ordinal() to typeOID
*/
static inline Oid
getTypeOidFromJavaEnumOrdinal(int8 enumType)
{
switch (enumType)
{
case 0:
return INT8OID;
case 1:
return BOOLOID;
case 2:
return FLOAT8OID;
case 3:
return INT4OID;
case 4:
return FLOAT4OID;
case 5:
return INT2OID;
case 6:
return BYTEAOID;
case 7:
return TEXTOID;
default:
ereport(ERROR,
(errcode(ERRCODE_DATA_EXCEPTION),
errmsg("ill-formatted record: unknown Java Enum Ordinal (%d)",
enumType)));
}
return 0;
}
/*
* Helper to determine the size of the null byte array
*/
static int
getNullByteArraySize(int colCnt)
{
return (colCnt / 8) + (colCnt % 8 != 0 ? 1 : 0);
}
/*
* Helper routine to convert boolean array to byte array.
*
* This routine is aware of the attributes in the table, and it will only
* produce a result for valid columns (excludes dropped columns).
*/
static bits8 *
boolArrayToByteArray(bool *data, int len, int validlen, int *outlen, TupleDesc tupdesc)
{
int i,
j,
k;
bits8 *result;
*outlen = getNullByteArraySize(validlen);
result = palloc0(*outlen * sizeof(bits8));
for (i = 0, j = 0, k = 7; i < len; i++)
{
/* Ignore dropped attributes. */
Form_pg_attribute attr = getAttributeFromTupleDesc(tupdesc,i);
if (attr->attisdropped) continue;
result[j] |= (data[i] ? 1 : 0) << k--;
if (k < 0)
{
j++;
k = 7;
}
}
return result;
}
/*
* Helper routine to convert byte array to boolean array
* It'll write the output to booldata.
*
* This routine supports dropped columns, in the case of tables with dropped
* columns booldata's size will match the number of original columns
* (including dropped columns), and the source `data` will match the number
* of columns provided by the PXF server, so if there are dropped columns,
* PXF server will only provide the subset of columns. Below a graphical
* representation of the mapping.
*
* --------------------------------------------
* | col1 | col2 | col3 | col5 | col6 | input: *data
* --------------------------------------------
* | | | | └----------------⬎
* ↓ ↓ ↓ └----------------⬎ ↓
* -------------------------------------------------------------
* | col1 | col2 | col3 | col4 (dropped) | col5 | col6 | output: **booldata
* -------------------------------------------------------------
*/
static void
byteArrayToBoolArray(bits8 *data, int data_len, int len, bool **booldata, int boollen, TupleDesc tupdesc)
{
int i,
j,
k;
ENSURE_BUF_LEN(data_len, 0, len);
for (i = 0, j = 0, k = 7; i < boollen; i++)
{
/* Ignore dropped attributes. */
Form_pg_attribute attr = getAttributeFromTupleDesc(tupdesc,i);
if (attr->attisdropped)
{
(*booldata)[i] = true;
continue;
}
(*booldata)[i] = ((data[j] >> k--) & 0x01) == 1;
if (k < 0)
{
j++;
k = 7;
}
}
}
/*
* Verify external table definition matches to input data columns
*/
static void
verifyExternalTableDefinition(int16 ncolumns_remote, AttrNumber nvalidcolumns, AttrNumber ncolumns, TupleDesc tupdesc,
char *data_buf, int data_len, int *bufidx)
{
int i;
StringInfoData errMsg;
Oid input_type;
Oid defined_type;
int8 enumType;
if (ncolumns_remote != nvalidcolumns)
ereport(ERROR, (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
errmsg("input data column count (%d) did not match the external table definition",
ncolumns_remote)));
initStringInfo(&errMsg);
/* Extract Column Type and check against External Table definition */
for (i = 0; i < ncolumns; i++)
{
Form_pg_attribute attr = getAttributeFromTupleDesc(tupdesc,i);
/* Ignore dropped attributes. */
if (attr->attisdropped) continue;
input_type = 0;
defined_type = attr->atttypid;
enumType = readInt1FromBuffer(data_buf, data_len, bufidx);
/* Convert enumType to type oid */
input_type = getTypeOidFromJavaEnumOrdinal(enumType);
if ((isBinaryFormatType(defined_type) || isBinaryFormatType(input_type)) &&
input_type != defined_type)
{
char *intype = format_type_be(input_type);
char *deftype = format_type_be(defined_type);
char *attname = NameStr(attr->attname);
if (errMsg.len > 0)
appendStringInfoString(&errMsg, ", ");
appendStringInfo(&errMsg, "column \"%s\" (type \"%s\", input data type \"%s\")",
attname, deftype, intype);
pfree(intype);
pfree(deftype);
}
}
if (errMsg.len > 0)
{
ereport(ERROR,
(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
errmsg("external table definition did not match input data: %s",
errMsg.data)));
}
}
Datum
gpdbwritableformatter_export(PG_FUNCTION_ARGS)
{
HeapTupleHeader rec = PG_GETARG_HEAPTUPLEHEADER(0);
TupleDesc tupdesc;
HeapTupleData tuple;
format_t *myData;
int datlen;
AttrNumber ncolumns;
AttrNumber nvalidcolumns;
AttrNumber i;
MemoryContext per_row_ctx,
oldcontext;
bits8 *nullBit;
int nullBitLen;
int endpadding;
/* Must be called via the external table format manager */
if (!CALLED_AS_FORMATTER(fcinfo))
ereport(ERROR, (errcode(ERRCODE_EXTERNAL_ROUTINE_EXCEPTION),
errmsg("cannot execute gpdbwritableformatter_export outside format manager")));
tupdesc = FORMATTER_GET_TUPDESC(fcinfo);
/* Get our internal description of the formatter */
ncolumns = tupdesc->natts;
myData = (format_t *) FORMATTER_GET_USER_CTX(fcinfo);
/* Get the number of valid columns, excludes dropped columns */
nvalidcolumns = 0;
for (i = 0; i < ncolumns; i++)
{
Form_pg_attribute attr = getAttributeFromTupleDesc(tupdesc,i);
if (!attr->attisdropped)
nvalidcolumns++;
}
/*
* Initialize the context structure
*/
if (myData == NULL)
{
// FORMATTER_GET_EXTENCODING(fcinfo) may return database encoding; use table encoding instead.
Relation rel = FORMATTER_GET_RELATION(fcinfo);
if (rel == NULL)
{
ereport(ERROR, (errcode(ERRCODE_EXTERNAL_ROUTINE_EXCEPTION),
errmsg(FORMATTER_ENCODING_ERR_MSG, "export")));
}
ExtTableEntry *exttbl = GetExtTableEntry(rel->rd_id);
if (exttbl->encoding != PG_UTF8)
{
ereport(ERROR, (errcode(ERRCODE_EXTERNAL_ROUTINE_EXCEPTION),
errmsg(FORMATTER_ENCODING_ERR_MSG, "export")));
}
myData = palloc(sizeof(format_t));
myData->values = palloc(sizeof(Datum) * ncolumns);
myData->outval = palloc(sizeof(char *) * ncolumns);
myData->nulls = palloc(sizeof(bool) * ncolumns);
myData->outlen = palloc(sizeof(int) * ncolumns);
myData->outpadlen = palloc(sizeof(int) * ncolumns);
myData->io_functions = palloc(sizeof(FmgrInfo) * ncolumns);
myData->export_format_tuple = makeStringInfo();
/* setup the text/binary input function */
for (i = 0; i < ncolumns; i++)
{
Form_pg_attribute attr = getAttributeFromTupleDesc(tupdesc,i);
Oid type = attr->atttypid;
bool isvarlena;
Oid functionId;
/* Ignore dropped attributes. */
if (attr->attisdropped)
continue;
/* Get the text/binary "send" function */
if (isBinaryFormatType(type))
getTypeBinaryOutputInfo(type, &(functionId), &isvarlena);
else
getTypeOutputInfo(type, &(functionId), &isvarlena);
fmgr_info(functionId, &(myData->io_functions[i]));
}
FORMATTER_SET_USER_CTX(fcinfo, myData);
}
per_row_ctx = FORMATTER_GET_PER_ROW_MEM_CTX(fcinfo);
oldcontext = MemoryContextSwitchTo(per_row_ctx);
/* break the input tuple into fields */
tuple.t_len = HeapTupleHeaderGetDatumLength(rec);
ItemPointerSetInvalid(&(tuple.t_self));
tuple.t_data = rec;
heap_deform_tuple(&tuple, tupdesc, myData->values, myData->nulls);
/*
* Starting from here. The conversion to bytes is exactly the same as
* GPDBWritable.toBytes()
*/
/*-----
* Now, compute the total payload and header length (#col excludes
* dropped columns):
*
* header = total length (4 byte), Version (2 byte), Error (1 byte), #col (2 byte)
* col type array = #col * 1 byte
* null bit array = ceil(#col/8)
*-----
*/
datlen = sizeof(int32) + sizeof(int16) + sizeof(int8) + sizeof(int16);
datlen += nvalidcolumns;
datlen += getNullByteArraySize(nvalidcolumns);
/*
* We need to know the total length of the tuple. So, we've to transformed
* each column so that we know the transformed size and the alignment
* padding.
*
* Since we're computing the conversion function, we use per-row memory
* context inside the loop.
*/
for (i = 0; i < ncolumns; i++)
{
Form_pg_attribute attr = getAttributeFromTupleDesc(tupdesc,i);
/* Ignore dropped attributes. */
if (attr->attisdropped) continue;
Oid type = attr->atttypid;
Datum val = myData->values[i];
bool nul = myData->nulls[i];
FmgrInfo *iofunc = &(myData->io_functions[i]);
int alignpadlen = 0;
if (nul)
myData->outlen[i] = 0;
else
{
if (isBinaryFormatType(type))
{
bytea *tmpval = SendFunctionCall(iofunc, val);;
/* NOTE: exclude the header length */
myData->outval[i] = VARDATA(tmpval);
myData->outlen[i] = VARSIZE_ANY_EXHDR(tmpval);
}
else
{
/* NOTE: include the "\0" in the length for text format */
myData->outval[i] = OutputFunctionCall(iofunc, val);
myData->outlen[i] = strlen(myData->outval[i]) + 1;
}
/*
* For variable length type, we added a 4 byte length header. So,
* it'll be aligned int4. For fixed length type, we'll use the
* type alignment.
*/
if (isVariableLength(type))
{
alignpadlen = INTALIGN(datlen) - datlen;
datlen += sizeof(int32);
}
else
alignpadlen = att_align_nominal(datlen, attr->attalign) - datlen;
myData->outpadlen[i] = alignpadlen;
datlen += alignpadlen;
}
datlen += myData->outlen[i];
}
/*
* Add the final alignment padding for the next record
*/
endpadding = DOUBLEALIGN(datlen) - datlen;
datlen += endpadding;
/*
* Now, we're done with per-row computation. Switch back to the old memory
* context.
*/
MemoryContextSwitchTo(oldcontext);
/*
* Resize buffer, if needed The new size includes the 4 byte VARHDSZ, the
* entire payload and 1 more byte for '\0' that StringInfo always ends
* with.
*/
if (myData->export_format_tuple->maxlen < VARHDRSZ + datlen + 1)
{
pfree(myData->export_format_tuple->data);
initStringInfoOfSize(myData->export_format_tuple, VARHDRSZ + datlen + 1);
}
/* Reset the export format buffer */
resetStringInfo(myData->export_format_tuple);
/* Reserve VARHDRSZ bytes for the bytea length word */
appendStringInfoFill(myData->export_format_tuple, VARHDRSZ, '\0');
/* Construct the packet header */
appendIntToBuffer(myData->export_format_tuple, datlen);
appendInt2ToBuffer(myData->export_format_tuple, GPDBWRITABLE_VERSION);
appendInt1ToBuffer(myData->export_format_tuple, 0); /* error */
appendInt2ToBuffer(myData->export_format_tuple, nvalidcolumns);
/* Write col type for columns that have not been dropped */
for (i = 0; i < ncolumns; i++)
{
Form_pg_attribute attr = getAttributeFromTupleDesc(tupdesc,i);
/* Ignore dropped attributes. */
if (!attr->attisdropped)
{
appendInt1ToBuffer(myData->export_format_tuple,
getJavaEnumOrdinal(attr->atttypid));
}
}
/* Write Nullness */
nullBit = boolArrayToByteArray(myData->nulls, ncolumns, nvalidcolumns, &nullBitLen, tupdesc);
appendBinaryStringInfo(myData->export_format_tuple, (char *) nullBit, nullBitLen);
/* Column Value */
for (i = 0; i < ncolumns; i++)
{
Form_pg_attribute attr = getAttributeFromTupleDesc(tupdesc,i);
/* Ignore dropped attributes and null values. */
if (!attr->attisdropped && !myData->nulls[i])
{
/* Pad the alignment byte first */
appendStringInfoFill(myData->export_format_tuple, myData->outpadlen[i], '\0');
/* For variable length type, we added a 4 byte length header */
if (isVariableLength(attr->atttypid))
appendIntToBuffer(myData->export_format_tuple, myData->outlen[i]);
/* Now, write the actual column value */
appendBinaryStringInfo(myData->export_format_tuple,
myData->outval[i], myData->outlen[i]);
}
}
/* End padding */
appendStringInfoFill(myData->export_format_tuple, endpadding, '\0');
if (myData->export_format_tuple->len != datlen + VARHDRSZ)
{
ereport(ERROR, (errcode(ERRCODE_EXTERNAL_ROUTINE_EXCEPTION),
errmsg("Tuple length doesn't match the data length")));
}
SET_VARSIZE(myData->export_format_tuple->data, datlen + VARHDRSZ);
PG_RETURN_BYTEA_P(myData->export_format_tuple->data);
}
Datum
gpdbwritableformatter_import(PG_FUNCTION_ARGS)
{
HeapTuple tuple;
TupleDesc tupdesc;
MemoryContext per_row_ctx,
oldcontext;
format_t *myData;
AttrNumber ncolumns;
AttrNumber nvalidcolumns = 0;
AttrNumber i;
char *data_buf;
int data_cur;
int data_len;
int tuplelen;
int bufidx = 0;
int tupleEndIdx = 0;
int16 version;
int8 error_flag = 0;
int16 ncolumns_remote = 0;
int remaining = 0;
/* Must be called via the external table format manager */
if (!CALLED_AS_FORMATTER(fcinfo))
ereport(ERROR, (errcode(ERRCODE_EXTERNAL_ROUTINE_EXCEPTION),
errmsg("cannot execute gpdbwritableformatter_import outside format manager")));
tupdesc = FORMATTER_GET_TUPDESC(fcinfo);
/* Get our internal description of the formatter */
ncolumns = tupdesc->natts;
myData = (format_t *) FORMATTER_GET_USER_CTX(fcinfo);
/* Get the number of valid columns, excluding dropped columns */
for (i = 0; i < ncolumns; i++)
{
Form_pg_attribute attr = getAttributeFromTupleDesc(tupdesc,i);
if (!attr->attisdropped)
nvalidcolumns++;
}
/*
* Initialize the context structure
*/
if (myData == NULL)
{
// FORMATTER_GET_EXTENCODING(fcinfo) may return database encoding; use table encoding instead.
Relation rel = FORMATTER_GET_RELATION(fcinfo);
if (rel == NULL)
{
ereport(ERROR, (errcode(ERRCODE_EXTERNAL_ROUTINE_EXCEPTION),
errmsg(FORMATTER_ENCODING_ERR_MSG, "import")));
}
ExtTableEntry *exttbl = GetExtTableEntry(rel->rd_id);
if (exttbl->encoding != PG_UTF8)
{
ereport(ERROR, (errcode(ERRCODE_EXTERNAL_ROUTINE_EXCEPTION),
errmsg(FORMATTER_ENCODING_ERR_MSG, "import")));
}
myData = palloc(sizeof(format_t));
myData->values = palloc(sizeof(Datum) * ncolumns);
myData->nulls = palloc(sizeof(bool) * ncolumns);
myData->lineno = FIRST_LINE_NUM;
myData->outlen = palloc(sizeof(int) * ncolumns);
myData->typioparams = (Oid *) palloc(ncolumns * sizeof(Oid));
myData->io_functions = palloc(sizeof(FmgrInfo) * ncolumns);
for (i = 0; i < ncolumns; i++)
{
Form_pg_attribute attr = getAttributeFromTupleDesc(tupdesc,i);
Oid type = attr->atttypid;
Oid functionId;
/* Ignore dropped attributes. */
if (attr->attisdropped)
continue;
/* Get the text/binary "receive" function */
if (isBinaryFormatType(type))
getTypeBinaryInputInfo(type, &(functionId), &myData->typioparams[i]);
else
getTypeInputInfo(type, &(functionId), &myData->typioparams[i]);
fmgr_info(functionId, &(myData->io_functions[i]));
}
FORMATTER_SET_USER_CTX(fcinfo, myData);
}
/* get our input data buf and number of valid bytes in it */
data_buf = FORMATTER_GET_DATABUF(fcinfo);
data_len = FORMATTER_GET_DATALEN(fcinfo);
data_cur = FORMATTER_GET_DATACURSOR(fcinfo);
/*--------------------------------
* MAIN FORMATTING CODE
*--------------------------------
*/
/* Get the first 4 byte; That's the length of the entire packet */
remaining = data_len - data_cur;
bufidx = data_cur;
/*
* NOTE: Unexpected EOF Error Handling
*
* The first time we noticed an unexpected EOF, we'll set the datacursor
* forward and then raise the error. But then, the framework will still
* call the formatter the function again. Now, the formatter function will
* be provided with a zero length data buffer. In this case, we should not
* raise an error again, but simply return "NEED MORE DATA". This is how
* the formatter framework works.
*/
if (remaining == 0 && FORMATTER_GET_SAW_EOF(fcinfo))
FORMATTER_RETURN_NOTIFICATION(fcinfo, FMT_NEED_MORE_DATA);
if (remaining < 4)
{
if (FORMATTER_GET_SAW_EOF(fcinfo))
{
FORMATTER_SET_BAD_ROW_DATA(fcinfo, data_buf + data_cur, remaining);
ereport(ERROR, (errcode(ERRCODE_DATA_EXCEPTION),
errmsg("unexpected end of file")));
}
FORMATTER_RETURN_NOTIFICATION(fcinfo, FMT_NEED_MORE_DATA);
}
tuplelen = readIntFromBuffer(data_buf, data_len, &bufidx);
/* calculate the index of last byte of this tuple in data_buf */
tupleEndIdx = data_cur + tuplelen;
/* Now, make sure we've received the entire tuple */
if (remaining < tuplelen)
{
if (FORMATTER_GET_SAW_EOF(fcinfo))
{
FORMATTER_SET_BAD_ROW_DATA(fcinfo, data_buf + data_cur, remaining);
ereport(ERROR, (errcode(ERRCODE_DATA_EXCEPTION),
errmsg("unexpected end of file")));
}
FORMATTER_RETURN_NOTIFICATION(fcinfo, FMT_NEED_MORE_DATA);
}
/* We got here. So, we've the ENTIRE tuple in the buffer */
FORMATTER_SET_BAD_ROW_DATA(fcinfo, data_buf + data_cur, tuplelen);
/* start clean */
MemSet(myData->values, 0, ncolumns * sizeof(Datum));
MemSet(myData->nulls, true, ncolumns * sizeof(bool));
per_row_ctx = FORMATTER_GET_PER_ROW_MEM_CTX(fcinfo);
oldcontext = MemoryContextSwitchTo(per_row_ctx);
/* extract the version, error and column count */
version = readInt2FromBuffer(data_buf, tupleEndIdx, &bufidx);
if ((version != GPDBWRITABLE_VERSION) && (version != GPDBWRITABLE_PREV_VERSION))
ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("cannot import data version %d", version)));
if (version == GPDBWRITABLE_VERSION)
error_flag = readInt1FromBuffer(data_buf, tupleEndIdx, &bufidx);
if (error_flag) {
bufidx += ERR_COL_OFFSET;
ereport(ERROR, (errcode(ERRCODE_DATA_EXCEPTION),
errmsg("%.*s", tupleEndIdx - bufidx, data_buf + bufidx)));
}
ncolumns_remote = readInt2FromBuffer(data_buf, tupleEndIdx, &bufidx);
verifyExternalTableDefinition(ncolumns_remote, nvalidcolumns, ncolumns, tupdesc, data_buf, tupleEndIdx, &bufidx);
/* Extract null bit array */
{
int nullByteLen = getNullByteArraySize(ncolumns_remote);
bits8 *nullByteArray = (bits8 *) (data_buf + bufidx);
byteArrayToBoolArray(nullByteArray, tupleEndIdx - bufidx, nullByteLen, &myData->nulls, ncolumns, tupdesc);
bufidx += nullByteLen;
}
/* extract column value */
for (i = 0; i < ncolumns; i++)
{
Form_pg_attribute attr = getAttributeFromTupleDesc(tupdesc, i);
/* Ignore dropped attributes. */
if (attr->attisdropped)
continue;
if (!myData->nulls[i])
{
FmgrInfo *iofunc = &(myData->io_functions[i]);
/*
* Skip the alignment padding for variable length type: always
* align int4 because we're reading a length header. we'll get the
* payload length from the first 4 byte.
*/
if (isVariableLength(attr->atttypid))
{
bufidx = INTALIGN(bufidx);
myData->outlen[i] = readIntFromBuffer(data_buf, tupleEndIdx, &bufidx);
}
/*
* Skip the alignment padding for fixed length type: use the type
* alignment. we can use the type length attribute.
*/
else
{
bufidx = att_align_nominal(bufidx, attr->attalign);
myData->outlen[i] = attr->attlen;
}
/* check that the length of the i-th value is not negative and fits
* within the remaining space for the current tuple in data_buf
*/
if (myData->outlen[i] < 0 || (tupleEndIdx - bufidx) < myData->outlen[i])
ereport(FATAL,
(errcode(ERRCODE_EXTERNAL_ROUTINE_EXCEPTION),
errmsg("data for column %d of row %d has invalid length",
i + 1, myData->lineno),
errdetail("total length for tuple is %d bytes, remaining bytes is %d, length for column %d is %d bytes",
tuplelen, tupleEndIdx - bufidx, i + 1, myData->outlen[i])));
if (isBinaryFormatType(attr->atttypid))
{
StringInfoData tmpbuf;
tmpbuf.data = data_buf + bufidx;
tmpbuf.maxlen = myData->outlen[i];
tmpbuf.len = myData->outlen[i];
tmpbuf.cursor = 0;
myData->values[i] = ReceiveFunctionCall(iofunc,
&tmpbuf,
myData->typioparams[i],
attr->atttypmod);
}
else
{
/*
* Read string value from data_buf and convert it to internal representation
*
* PXF service should send all strings with a terminating nul-byte, so we can
* determine the number of bytes that the input function will read and compare
* it with the length that the PXF service computed and sent. Since we've
* checked that outlen[i] is no larger than the number of bytes left for this
* tuple in data_buf, we use it as max number of bytes to scan in the call to
* strnlen
*/
size_t actual_len = strnlen(data_buf + bufidx, myData->outlen[i]);
/*
* Compare the length as returned by strnlen with the length as determined by
* the PXF service. If the source data includes ASCII NUL-byte in the string,
* then these two values will not match. It's possible that this will result
* in the Postgres input function truncating/mis-reading the value. Rather
* than treat this as an error here, we log a debug message.
*/
if (actual_len != myData->outlen[i]-1)
ereport(DEBUG1,
(errmsg("expected column %d of row %d to have length %d, actual length is %ld", i+1, myData->lineno, myData->outlen[i]-1, actual_len)));
myData->values[i] = InputFunctionCall(iofunc,
data_buf + bufidx,
myData->typioparams[i],
attr->atttypmod);
}
bufidx += myData->outlen[i];
}
}
bufidx = DOUBLEALIGN(bufidx);
if (tupleEndIdx != bufidx)
ereport(ERROR,
(errcode(ERRCODE_EXTERNAL_ROUTINE_EXCEPTION),
errmsg("tuplelen != bufidx: %d:%d:%d", tuplelen, bufidx, data_cur)));
data_cur += tuplelen;
MemoryContextSwitchTo(oldcontext);
FORMATTER_SET_DATACURSOR(fcinfo, data_cur);
tuple = heap_form_tuple(tupdesc, myData->values, myData->nulls);
FORMATTER_SET_TUPLE(fcinfo, tuple);
FORMATTER_RETURN_TUPLE(tuple);
}
static inline Form_pg_attribute
getAttributeFromTupleDesc(TupleDesc tupdesc, int index)
{
Form_pg_attribute attr;
attr = &tupdesc->attrs[index];
return attr;
}