blob: 39671cb1b81c1c56ae9e3a28e7b7d96f61697f8f [file]
/*
* 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));
}