/*
 * 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 "pxffilters.h"
#include "pxfheaders.h"
#include "commands/defrem.h"
#include "access/external.h"
#include "extension/gp_exttable_fdw/extaccess.h"
#include "executor/execExpr.h"
#include "utils/timestamp.h"
#include "nodes/makefuncs.h"
#include "cdb/cdbvars.h"

/* helper function declarations */
static void add_alignment_size_httpheader(CHURL_HEADERS headers);
static void add_tuple_desc_httpheader(CHURL_HEADERS headers, Relation rel);
static void add_location_options_httpheader(CHURL_HEADERS headers, GPHDUri *gphduri);
static char *get_format_name(ExtTableEntry *exttbl);
static char *getFormatterString(ExtTableEntry *exttbl);
static bool isFormatterPxfDelimited(ExtTableEntry *exttbl);
static bool isFormatterPxfWritable(ExtTableEntry *exttbl);
static void add_projection_desc_httpheader_pg12(CHURL_HEADERS headers, ProjectionInfo *projInfo, List *qualsAttributes, Relation rel);

static bool add_attnums_from_targetList(Node *node, List *attnums);
static void add_projection_index_header(CHURL_HEADERS pVoid, StringInfoData data, int attno, char number[32]);
static List *appendCopyEncodingOptionToList(List *copyFmtOpts, int encoding);
static int  *getVarNumbers(ProjectionInfo *projInfo);
static List *getTargetList(ProjectionInfo *projInfo);
static bool needToIterateTargetList(List *targetList, int *varNumbers);
static Node *getTargetListEntryExpression(ListCell *lc1);
static int  getNumSimpleVars(ProjectionInfo *projInfo);

/*
 * Add key/value pairs to connection header.
 * These values are the context of the query and used
 * by the remote component.
 */
void
build_http_headers(PxfInputData *input)
{
	extvar_t       ev;
	CHURL_HEADERS  headers    = input->headers;
	GPHDUri        *gphduri   = input->gphduri;
	Relation       rel        = input->rel;
	char           *filterstr = input->filterstr;
	char           *data_encoding = NULL;
	char           long_number[sizeof(int32) * 8];
	ProjectionInfo *proj_info = input->proj_info;
	const char	   *relname;
	char		   *relnamespace = NULL;

	relname = gphduri->data;
	if (rel != NULL)
	{
		/* format */
		ExtTableEntry *exttbl = GetExtTableEntry(rel->rd_id);
		ListCell   *option;
		List	   *copyFmtOpts = NIL;

		// in the case of PxfDelimitedFormatter formatter, the only viable profiles are *:text and *:csv.
		// error out early here if the profile is not accepted
		if (getFormatterString(exttbl) && // if the formatter string is non empty
			isFormatterPxfDelimited(exttbl) && // and the formatter is PxfDelimitedFormatter
			(!input->gphduri->profile || // if the profile is empty OR
				(!strstr(input->gphduri->profile, ":text") && // the profile is neither text
					!strstr(input->gphduri->profile, ":csv")))) // nor csv
		{
			ereport(ERROR,
					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
					 errmsg("The \"%s\" formatter only works with *:text or *:csv profiles.", PxfDelimitedFormatter),
					 errhint("Please double check the profile option in the external table definition.")));
		}

		/* pxf treats everything but pxfwritable_[import|export] as TEXT (even CSV) */
		char *format = get_format_name(exttbl);

		churl_headers_append(headers, "X-GP-FORMAT", format);

    /* Parse fmtOptString here */
    if (fmttype_is_text(exttbl->fmtcode) || fmttype_is_csv(exttbl->fmtcode))
    {
      copyFmtOpts = exttbl->options;
    }

		/* pass external table's encoding to copy's options */
		copyFmtOpts = appendCopyEncodingOptionToList(copyFmtOpts, exttbl->encoding);

		/* Extract options from the statement node tree */
		foreach(option, copyFmtOpts)
		{
			DefElem    *def = (DefElem *) lfirst(option);

			if (strcmp(def->defname, "encoding") == 0)
			{
				data_encoding = defGetString(def);
			}
			else
			{
				churl_headers_append(headers, normalize_key_name(def->defname), defGetString(def));
			}
		}

		/* Record fields - name and type of each field */
		add_tuple_desc_httpheader(headers, rel);

		relname = RelationGetRelationName(rel);
		relnamespace = GetNamespaceName(RelationGetNamespace(rel));
	}

	if (proj_info != NULL)
	{
		bool qualsAreSupported = true;
		List *qualsAttributes =
				 extractPxfAttributes(input->quals, &qualsAreSupported);
		/* projection information is incomplete if columns from WHERE clause wasn't extracted */
		/* if any of expressions in WHERE clause is not supported - do not send any projection information at all*/
		if (qualsAreSupported &&
			(qualsAttributes != NIL || list_length(input->quals) == 0))
		{
			add_projection_desc_httpheader_pg12(headers, proj_info, qualsAttributes, rel);
		}
		else
		{
			elog(DEBUG2,
				 "Query will not be optimized to use projection information");
		}
	}

	/* GP cluster configuration */
	external_set_env_vars(&ev, gphduri->uri, false, NULL, NULL, false, 0);

	/* make sure that user identity is known and set, otherwise impersonation by PXF will be impossible */
	if (!ev.GP_USER || !ev.GP_USER[0])
		ereport(ERROR,
				(errcode(ERRCODE_INTERNAL_ERROR),
				 errmsg("user identity is unknown")));
	churl_headers_append(headers, "X-GP-ENCODED-HEADER-VALUES", "true");
	churl_headers_append(headers, "X-GP-USER", ev.GP_USER);

	churl_headers_append(headers, "X-GP-SEGMENT-ID", ev.GP_SEGMENT_ID);
	churl_headers_append(headers, "X-GP-SEGMENT-COUNT", ev.GP_SEGMENT_COUNT);
	churl_headers_append(headers, "X-GP-XID", ev.GP_XID);
	churl_headers_append(headers, "X-GP-PXF-API-VERSION", PXF_API_VERSION);

	pg_ltoa(gp_session_id, long_number);
	churl_headers_append(headers, "X-GP-SESSION-ID", long_number);
	pg_ltoa(gp_command_count, long_number);
	churl_headers_append(headers, "X-GP-COMMAND-COUNT", long_number);

	add_alignment_size_httpheader(headers);

	/* headers for uri data */
	churl_headers_append(headers, "X-GP-URL-HOST", gphduri->host);
	churl_headers_append(headers, "X-GP-URL-PORT", gphduri->port);
	churl_headers_append(headers, "X-GP-DATA-DIR", gphduri->data);
	churl_headers_append(headers, "X-GP-TABLE-NAME", relname);
	churl_headers_append(headers, "X-GP-SCHEMA-NAME", relnamespace);

	/* encoding options */
	churl_headers_append(headers, "X-GP-DATA-ENCODING", data_encoding);
	churl_headers_append(headers, "X-GP-DATABASE-ENCODING", GetDatabaseEncodingName());

	/* location options */
	add_location_options_httpheader(headers, gphduri);

	/* full uri */
	churl_headers_append(headers, "X-GP-URI", gphduri->uri);

	/* filters */
	if (filterstr != NULL)
	{
		churl_headers_append(headers, "X-GP-FILTER", filterstr);
		churl_headers_append(headers, "X-GP-HAS-FILTER", "1");
	}
	else
		churl_headers_append(headers, "X-GP-HAS-FILTER", "0");

	// Since we only establish a single connection per segment, we can safely close the connection after
	// the segment completes streaming data.
	churl_headers_override(headers, "Connection", "close");
}

/* Report alignment size to remote component
 * GPDBWritable uses alignment that has to be the same as
 * in the C code.
 * Since the C code can be compiled for both 32 and 64 bits,
 * the alignment can be either 4 or 8.
 */
static void
add_alignment_size_httpheader(CHURL_HEADERS headers)
{
	char		tmp[sizeof(char *)];

	pg_ltoa(sizeof(char *), tmp);
	churl_headers_append(headers, "X-GP-ALIGNMENT", tmp);
}

/*
 * Report tuple description to remote component
 * Currently, number of attributes, attributes names, types and types modifiers
 * Each attribute has a pair of key/value
 * where X is the number of the attribute
 * X-GP-ATTR-NAMEX - attribute X's name
 * X-GP-ATTR-TYPECODEX - attribute X's type OID (e.g, 16)
 * X-GP-ATTR-TYPENAMEX - attribute X's type name (e.g, "boolean")
 * optional - X-GP-ATTR-TYPEMODX-COUNT - total number of modifier for attribute X
 * optional - X-GP-ATTR-TYPEMODX-Y - attribute X's modifiers Y (types which have precision info, like numeric(p,s))
 *
 * If a column has been dropped from the external table definition, that
 * column will not be reported to the PXF server (as if it never existed).
 * For example:
 *
 *  ---------------------------------------------
 * |  col1  |  col2  |  col3 (dropped)  |  col4  |
 *  ---------------------------------------------
 *
 * Col4 will appear as col3 to the PXF server as if col3 never existed, and
 * only 3 columns will be reported to PXF server.
 */
static void
add_tuple_desc_httpheader(CHURL_HEADERS headers, Relation rel)
{
	int				i, attrIx;
	char			long_number[sizeof(int32) * 8];
	StringInfoData	formatter;
	TupleDesc		tuple;

	initStringInfo(&formatter);

	/* Get tuple description itself */
	tuple = RelationGetDescr(rel);

	/* Iterate attributes */
	for (i = 0, attrIx = 0; i < tuple->natts; ++i)
	{
		FormData_pg_attribute *attribute = &tuple->attrs[i];

		/* Ignore dropped attributes. */
		if (attribute->attisdropped)
			continue;

		/* Add a key/value pair for attribute name */
		resetStringInfo(&formatter);
		appendStringInfo(&formatter, "X-GP-ATTR-NAME%u", attrIx);
		churl_headers_append(headers, formatter.data, attribute->attname.data);

		/* Add a key/value pair for attribute type */
		resetStringInfo(&formatter);
		appendStringInfo(&formatter, "X-GP-ATTR-TYPECODE%u", attrIx);
		pg_ltoa(attribute->atttypid, long_number);
		churl_headers_append(headers, formatter.data, long_number);

		/* Add a key/value pair for attribute type name */
		resetStringInfo(&formatter);
		appendStringInfo(&formatter, "X-GP-ATTR-TYPENAME%u", attrIx);
		churl_headers_append(headers, formatter.data, TypeOidGetTypename(attribute->atttypid));

		/* Add attribute type modifiers if any */
		if (attribute->atttypmod > -1)
		{
			switch (attribute->atttypid)
			{
				case NUMERICOID:
				case NUMERIC_ARRAY_OID:
					{
						resetStringInfo(&formatter);
						appendStringInfo(&formatter, "X-GP-ATTR-TYPEMOD%u-COUNT", attrIx);
						pg_ltoa(2, long_number);
						churl_headers_append(headers, formatter.data, long_number);


						/* precision */
						resetStringInfo(&formatter);
						appendStringInfo(&formatter, "X-GP-ATTR-TYPEMOD%u-%u", attrIx, 0);
						pg_ltoa((attribute->atttypmod >> 16) & 0xffff, long_number);
						churl_headers_append(headers, formatter.data, long_number);

						/* scale */
						resetStringInfo(&formatter);
						appendStringInfo(&formatter, "X-GP-ATTR-TYPEMOD%u-%u", attrIx, 1);
						pg_ltoa((attribute->atttypmod - VARHDRSZ) & 0xffff, long_number);
						churl_headers_append(headers, formatter.data, long_number);
						break;
					}
				case CHAROID:
				case CHAR_ARRAY_OID:
				case BPCHAROID:
				case BPCHAR_ARRAY_OID:
				case VARCHAROID:
				case VARCHAR_ARRAY_OID:
					{
						resetStringInfo(&formatter);
						appendStringInfo(&formatter, "X-GP-ATTR-TYPEMOD%u-COUNT", attrIx);
						pg_ltoa(1, long_number);
						churl_headers_append(headers, formatter.data, long_number);

						resetStringInfo(&formatter);
						appendStringInfo(&formatter, "X-GP-ATTR-TYPEMOD%u-%u", attrIx, 0);
						pg_ltoa((attribute->atttypmod - VARHDRSZ), long_number);
						churl_headers_append(headers, formatter.data, long_number);
						break;
					}
				case VARBITOID:
				case VARBIT_ARRAY_OID:
				case BITOID:
				case BIT_ARRAY_OID:
				case TIMESTAMPOID:
				case TIMESTAMP_ARRAY_OID:
				case TIMESTAMPTZOID:
				case TIMESTAMPTZ_ARRAY_OID:
				case TIMEOID:
				case TIME_ARRAY_OID:
				case TIMETZOID:
				case TIMETZ_ARRAY_OID:
					{
						resetStringInfo(&formatter);
						appendStringInfo(&formatter, "X-GP-ATTR-TYPEMOD%u-COUNT", attrIx);
						pg_ltoa(1, long_number);
						churl_headers_append(headers, formatter.data, long_number);

						resetStringInfo(&formatter);
						appendStringInfo(&formatter, "X-GP-ATTR-TYPEMOD%u-%u", attrIx, 0);
						pg_ltoa((attribute->atttypmod), long_number);
						churl_headers_append(headers, formatter.data, long_number);
						break;
					}
				case INTERVALOID:
				case INTERVAL_ARRAY_OID:
					{
						resetStringInfo(&formatter);
						appendStringInfo(&formatter, "X-GP-ATTR-TYPEMOD%u-COUNT", attrIx);
						pg_ltoa(1, long_number);
						churl_headers_append(headers, formatter.data, long_number);

						resetStringInfo(&formatter);
						appendStringInfo(&formatter, "X-GP-ATTR-TYPEMOD%u-%u", attrIx, 0);
						pg_ltoa(INTERVAL_PRECISION(attribute->atttypmod), long_number);
						churl_headers_append(headers, formatter.data, long_number);
						break;
					}
				default:
					elog(DEBUG5, "add_tuple_desc_httpheader: unsupported type %d ", attribute->atttypid);
					break;
			}
		}
		attrIx++;
	}

	/* Convert the number of attributes to a string */
	pg_ltoa(attrIx, long_number);
	churl_headers_append(headers, "X-GP-ATTRS", long_number);

	pfree(formatter.data);
}

/*
 * Returns pre-computed array of simple var attrnos, if available
 */
static inline int*
getVarNumbers(ProjectionInfo *projInfo) {
	return NULL; // does not exist in projInfo in GP7
}

/*
 * Returns targetList from provided ProjectionInfo
 */
static inline List*
getTargetList(ProjectionInfo *projInfo)
{
	return (List *) projInfo->pi_state.expr;
}

/*
 * Determines whether there is a need to iterate over the targetList to find projected attributes
 */
static inline bool
needToIterateTargetList(List *targetList, int *varNumbers)
{
	/*
	 * In GP6 non-simple Vars are added to the targetlist of ProjectionInfo while
	 * simple Vars are pre-computed and their attnos are placed into varNumbers array
	 * In GP7 everything is in targetList
	 */
	return (targetList != NULL);
}

/*
 * Returns expression for a targetList entry.
 */
static inline
Node *getTargetListEntryExpression(ListCell *lc1)
{
	ExprState *gstate = (ExprState *) lfirst(lc1);
	return (Node *) gstate;
};

/*
 * Returns a count of simpleVars if they were pre-computed.
 */
static inline
int getNumSimpleVars(ProjectionInfo *projInfo)
{
	// in GP7 there is no precomputation, the numSimpleVars stays at 0
	return 0;
};

/*
 * Report projection description to the remote component, the indices of
 * dropped columns do not get reported, as if they never existed, and
 * column indices that follow dropped columns will be shifted by the number
 * of dropped columns that precede it. For example,
 *
 *  ---------------------------------------------
 * |  col1  |  col2 (dropped)  |  col3  |  col4  |
 *  ---------------------------------------------
 *
 * Let's assume that col1 and col4 are projected, the reported projected
 * indices will be 0, 2. This is because we use 0-based indexing and because
 * col2 was dropped, the indices for col3 and col4 get shifted by -1.
 */

/*
 * Report projection description to the remote component, the indices of
 * dropped columns do not get reported, as if they never existed, and
 * column indices that follow dropped columns will be shifted by the number
 * of dropped columns that precede it. For example,
 *
 *  ---------------------------------------------
 * |  col1  |  col2 (dropped)  |  col3  |  col4  |
 *  ---------------------------------------------
 *
 * Let's assume that col1 and col4 are projected, the reported projected
 * indices will be 0, 2. This is because we use 0-based indexing and because
 * col2 was dropped, the indices for col3 and col4 get shifted by -1.
 */

static void
set_var_number(int **varNumbers, int *varNumberSize, int value, int offset)
{
	if (*varNumberSize == 0)
	{
		*varNumberSize = Max(32, offset * 2);
		*varNumbers = (int *) palloc0(sizeof(int) * (*varNumberSize));
	}
	else if (*varNumberSize <= offset)
	{
		*varNumberSize = Max(*varNumberSize * 2, offset * 2);
		*varNumbers = (int *) repalloc(*varNumbers, sizeof(int) * (*varNumberSize));
	}

	(*varNumbers)[offset] = value;
}

static void
add_projection_desc_httpheader_pg12(CHURL_HEADERS headers,
                 ProjectionInfo *projInfo,
                 List *qualsAttributes,
                 Relation rel)
{
  int        i;
  int        dropped_count;
  int        number;
  int        numTargetList;
  char      long_number[sizeof(int32) * 8];
    // projInfo->pi_varNumbers doesn't exist in PG14+, so collect attribute numbers
    // by iterating on the simple vars.
  
  int       varNumbersSize = 0;
  int       *varNumbers = NULL;
  Bitmapset   *attrs_used;
  StringInfoData  formatter;
  TupleDesc   tupdesc;
  initStringInfo(&formatter);
  numTargetList = 0;

   List *targetList = (List *) projInfo->pi_state.expr;
     int  numSimpleVars = 0;

  int numNonSimpleVars = 0;

    // TODO Re-evaluate this logic and may be we don't need numNonSimpleVars
   for (int i = 0; i < projInfo->pi_state.steps_len; i++)
     {
         ExprEvalStep *step = &projInfo->pi_state.steps[i];
         ExprEvalOp opcode = ExecEvalStepOp(&projInfo->pi_state, step);
             if ( opcode == EEOP_ASSIGN_INNER_VAR ||
                     opcode == EEOP_ASSIGN_OUTER_VAR ||
                     opcode == EEOP_ASSIGN_SCAN_VAR)
                     numSimpleVars++;
             else if (opcode == EEOP_ASSIGN_TMP_MAKE_RO ||
                        opcode == EEOP_ASSIGN_TMP)
                     numNonSimpleVars++;
     }

  /*
   * Non-simpleVars are added to the targetlist
   * we use expression_tree_walker to access attrno information
   * we do it through a helper function add_attnums_from_targetList
   */
  if (targetList)
  {

    List     *l = lappend_int(NIL, 0);
    ListCell *lc1;

    foreach(lc1, targetList)
    {
      ExprState *gstate = (ExprState *) lfirst(lc1);
            add_attnums_from_targetList( (Node *) gstate, l);
    }

        int i=0;
    foreach(lc1, l)
    {
      int attno = lfirst_int(lc1);
      if (attno > InvalidAttrNumber)
      {
        add_projection_index_header(headers,
                      formatter, attno - 1, long_number);
        numTargetList++;
		set_var_number(&varNumbers, &varNumbersSize, attno, i);
		i++;
      }
    }

    list_free(l);
  }

  number = numTargetList + numSimpleVars + list_length(qualsAttributes);
  if (number == 0)
    return;

  attrs_used = NULL;

  /* Convert the number of projection columns to a string */
  pg_ltoa(number, long_number);
  churl_headers_append(headers, "X-GP-ATTRS-PROJ", long_number);

    for (i = 0; i < numSimpleVars ; i++)
  {
    attrs_used =
      bms_add_member(attrs_used,
                           varNumbers[i] - FirstLowInvalidHeapAttributeNumber);
  }

  ListCell *attribute = NULL;

  /*
   * AttrNumbers coming from quals
   */
  foreach(attribute, qualsAttributes)
  {
    AttrNumber attrNumber = (AttrNumber) lfirst_int(attribute);
    attrs_used =
      bms_add_member(attrs_used,
             attrNumber + 1 - FirstLowInvalidHeapAttributeNumber);
  }

  tupdesc = RelationGetDescr(rel);
  dropped_count = 0;

  for (i = 1; i <= tupdesc->natts; i++)
  {
    /* Ignore dropped attributes. */
    if (tupdesc->attrs[i - 1].attisdropped)
    {
      /* keep a counter of the number of dropped attributes */
      dropped_count++;
      continue;
    }

    if (bms_is_member(i - FirstLowInvalidHeapAttributeNumber, attrs_used))
    {
      /* Shift the column index by the running dropped_count */
      add_projection_index_header(headers, formatter,
                    i - 1 - dropped_count, long_number);
    }
  }

  if (varNumbers)
  	pfree(varNumbers);

  list_free(qualsAttributes);
  pfree(formatter.data);
  bms_free(attrs_used);
}


/*
 * Adds the projection index header for the given attno
 */
static void
add_projection_index_header(CHURL_HEADERS headers,
							StringInfoData str,
							int attno,
							char long_number[32])
{
	pg_ltoa(attno, long_number);
	resetStringInfo(&str);
	appendStringInfo(&str, "X-GP-ATTRS-PROJ-IDX");
	churl_headers_append(headers, str.data, long_number);
}

/*
 * The options in the LOCATION statement of "create external table"
 * FRAGMENTER=HdfsDataFragmenter&ACCESSOR=SequenceFileAccessor...
 */
static void
add_location_options_httpheader(CHURL_HEADERS headers, GPHDUri *gphduri)
{
	ListCell   *option = NULL;

	foreach(option, gphduri->options)
	{
		OptionData *data = (OptionData *) lfirst(option);
		char	   *x_gp_key = normalize_key_name(data->key);

		churl_headers_append(headers, x_gp_key, data->value);
		pfree(x_gp_key);
	}
}

/*
 * Converts a character code for the format name and the formatter name into a string
 * that represents PXF transport format (TEXT or GPDBWritable)
 */
static char *
get_format_name(ExtTableEntry *exttbl)
{
	char	   *formatName = NULL;

	if (fmttype_is_text(exttbl->fmtcode) || fmttype_is_csv(exttbl->fmtcode))
	{
		formatName = TextFormatName;
	}
	else if (fmttype_is_custom(exttbl->fmtcode))
	{
		// need to determine if the formatter is pxfwritable_import or pxfwritable_export which requires
		// us to send to PXF the value of on-the-wire format as "GPDBWritable"
		if (isFormatterPxfWritable(exttbl)) {
			formatName = GpdbWritableFormatName;
		}
		else
		{
			// treat other custom formatters (such as 'fixedwidth_in') as TEXT for PXF transport
			formatName = TextFormatName;
		}
	}
	else
	{
		ereport(ERROR,
				(errcode(ERRCODE_INTERNAL_ERROR),
				 errmsg("unable to get format name for format code: %c",
						exttbl->fmtcode)));
	}

	return formatName;
}

/*
 * Returns the name of the formatter (GP7)
 * Returns the string containing all format options information (GP6 & GP5)
 * Returns NULL otherwise
 */
static char*
getFormatterString(ExtTableEntry *exttbl)
{
	char *formatterNameSearchString = NULL;

	// for GP7 the custom formatter name is among all other options of the corresponding foreign table
	ListCell   *option;
	foreach (option, exttbl->options) {
		DefElem *defel = (DefElem *) lfirst(option);
		if (strcmp(defel->defname, "formatter") == 0) {
			formatterNameSearchString = defGetString(defel);
			break;
		}
	}

	return formatterNameSearchString;
}

/*
 * Checks if the custom formatter specified for the table is pxfdelimited_import
 */
static bool
isFormatterPxfDelimited(ExtTableEntry *exttbl)
{
	char *formatterNameSearchString = getFormatterString(exttbl);

	if (!formatterNameSearchString || !strlen(formatterNameSearchString))
	{
		ereport(ERROR,
				(errcode(ERRCODE_INTERNAL_ERROR),
						errmsg("cannot determine the name of a custom formatter")));
	}

	return strstr(formatterNameSearchString, PxfDelimitedFormatter) != NULL;
}
/*
 * Checks if the custom formatter specified for the table starts with pxfwritable_ prefix
 */
static bool
isFormatterPxfWritable(ExtTableEntry *exttbl)
{
	char *formatterNameSearchString = getFormatterString(exttbl);

	if (!formatterNameSearchString || !strlen(formatterNameSearchString))
	{
		ereport(ERROR,
				(errcode(ERRCODE_INTERNAL_ERROR),
						errmsg("cannot determine the name of a custom formatter")));
	}

	return strstr(formatterNameSearchString, PXFWritableFormatterPrefix) != NULL;
}

/*
 * Gets a list of attnums from the given Node
 * it uses expression_tree_walker to recursively
 * get the list
 */
static bool
add_attnums_from_targetList(Node *node, List *attnums)
{
	if (node == NULL)
		return false;
	if (IsA(node, Var))
	{
		Var        *variable = (Var *) node;
		AttrNumber attnum    = variable->varattno;

		lappend_int(attnums, attnum);
		return false;
	}

	/*
	 * Don't examine the arguments or filters of Aggrefs or WindowFunc/WindowRef,
	 * because those do not represent expressions to be evaluated within the
	 * overall targetlist's econtext.
	 */
	if (IsA(node, Aggref))
		return false;
	if (IsA(node, WindowFunc))
		return false;
	return expression_tree_walker(node,
								  add_attnums_from_targetList,
								  (void *) attnums);
}

/*
 * This function is copied from fileam.c::appendCopyEncodingOption() in the 6X_STABLE branch.
 * appendCopyEncodingOption() does not exist on GP5
 * and in GP7 it is now a part of gpcontrib/gp_exttable_fdw/extaccess.c that is not easily linkable,
 * so we just define it here with a different name for all versions to use
 */
static List *
appendCopyEncodingOptionToList(List *copyFmtOpts, int encoding) {
	return lappend(copyFmtOpts,
				   makeDefElem("encoding",
							   (Node *) makeString((char *) pg_encoding_to_char(encoding)),
							   -1));
}
