| /*------------------------------------------------------------------------- |
| * |
| * 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; |
| |
| } |