/*
 * Copyright (c) 2020 Huawei Technologies Co.,Ltd.
 *
 * openGauss is licensed under Mulan PSL v2.
 * You can use this software according to the terms and conditions of the Mulan PSL v2.
 * You may obtain a copy of Mulan PSL v2 at:
 *
 *          http://license.coscl.org.cn/MulanPSL2
 *
 * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
 * EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
 * MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
 * See the Mulan PSL v2 for more details.
 * -------------------------------------------------------------------------
 *
 * parse_hint.cpp
 *
 * IDENTIFICATION
 *    src/common/backend/parser/parse_hint.cpp
 *
 * -------------------------------------------------------------------------
 */

#include "postgres.h"
#include "knl/knl_variable.h"

#include "nodes/nodes.h"
#include "nodes/nodeFuncs.h"
#include "parser/parse_hint.h"
#include "parser/scansup.h"
#include "optimizer/prep.h"
#include "utils/builtins.h"
#include "utils/lsyscache.h"
#include "utils/numeric.h"
#include "utils/syscache.h"
#include "parser/parse_type.h"
#include "optimizer/var.h"

/* We must include  "parser/gramparse.h" after including "parser/parse_hint.h"
 * Otherwise, we will include the wrong header file.
 * Because we need to include hint_gram.hpp, not gram.hpp
 * Please see gramparse.h for the detail.
 */
#include "parser/gramparse.h"

#define NOTFOUNDRELNAME 0
#define AMBIGUOUSRELNAME (-1)

/* Too much skew value will cost to much memory and execution time, so we add a limit here. */
#define MAX_SKEW_NUM 10

/* free hint's base relnames if there are any */
#define HINT_FREE_RELNAMES(hint)                   \
    do {                                           \
        if ((hint)->base.relnames) {               \
            list_free_deep((hint)->base.relnames); \
        }                                          \
    } while (0)

typedef struct join_relid_relname {
    Relids relids;  /* Join relids*/
    List* rel_list; /* Join rel name.*/
} relid_relname;

static const char* KeywordDesc(HintKeyword keyword);
static void relnamesToBuf(List* relnames, StringInfo buf);
static void JoinMethodHintDesc(JoinMethodHint* hint, StringInfo buf);
static void LeadingHintDesc(LeadingHint* hint, StringInfo buf);
static void RowsHintDesc(RowsHint* hint, StringInfo buf);
static void StreamHintDesc(StreamHint* hint, StringInfo buf);
static void BlockNameHintDesc(BlockNameHint* hint, StringInfo buf);
static void ScanMethodHintDesc(ScanMethodHint* hint, StringInfo buf);
static void SkewHintDesc(SkewHint* hint, StringInfo buf);
static void find_unused_hint_to_buf(List* hint_list, StringInfo hint_buf);
static void JoinMethodHintDelete(JoinMethodHint* hint);
static void LeadingHintDelete(LeadingHint* hint);
static void RowsHintDelete(RowsHint* hint);
static void RewriteHintDelete(RewriteHint* hint);
static void GatherHintDelete(GatherHint* hint);
static void StreamHintDelete(StreamHint* hint);
static void BlockNameHintDelete(BlockNameHint* hint);
static void ScanMethodHintDelete(ScanMethodHint* hint);
static void SkewHintDelete(SkewHint* hint);
static void SkewHintTransfDelete(SkewHintTransf* hint);
static char* get_hints_from_comment(const char* comment_str);
static void drop_duplicate_blockname_hint(HintState* hstate);
static void drop_duplicate_join_hint(PlannerInfo* root, HintState* hstate);
static void drop_duplicate_stream_hint(PlannerInfo* root, HintState* hstate);
static void drop_duplicate_row_hint(PlannerInfo* root, HintState* hstate);
static void drop_duplicate_rewrite_hint(PlannerInfo* root, HintState* hstate);
static void drop_duplicate_gather_hint(PlannerInfo* root, HintState* hstate);
static void drop_duplicate_scan_hint(PlannerInfo* root, HintState* hstate);
static void drop_duplicate_skew_hint(PlannerInfo* root, HintState* hstate);
static void drop_duplicate_predpush_hint(PlannerInfo* root, HintState* hstate);
static void drop_duplicate_predpush_same_level_hint(PlannerInfo* root, HintState* hstate);
static int find_relid_aliasname(Query* parse, const char* aliasname, bool find_in_rtable = false);
static Relids create_bms_of_relids(
    PlannerInfo* root, Query* parse, Hint* hint, List* relnamelist = NIL, Relids currelids = NULL);
static List* set_hint_relids(PlannerInfo* root, Query* parse, List* l);
static void leading_to_join_hint(HintState* hstate, Relids join_relids, Relids inner_relids, List* relname_list);
static void transform_leading_hint(PlannerInfo* root, Query* parse, HintState* hstate);
static void transform_skew_hint(PlannerInfo* root, Query* parse, List* skew_hint_list);
static SkewHintTransf* set_skew_hint(PlannerInfo* root, Query* parse, SkewHint* skew_hint);
static void set_subquery_rel(
    Query* parse, RangeTblEntry* rte, SkewHintTransf* skew_hint_transf, RangeTblEntry* parent_rte = NULL);
static void set_base_rel(Query* parse, RangeTblEntry* rte, SkewHintTransf* skew_hint_transf);
static bool subquery_can_pull_up(RangeTblEntry* subquery_rte, Query* parse);
static void set_skew_column(PlannerInfo* root, Query* parse, SkewHintTransf* skew_hint_transf, Oid** col_typid);
static RangeTblEntry* find_column_in_rtable_subquery(Query* parse, const char* column_name, int* location);
static RangeTblEntry* find_column_in_rel_info_list(
    SkewHintTransf* skew_hint_transf, const char* column_name, int* count, int* location);
static TargetEntry* find_column_in_targetlist_by_location(RangeTblEntry* rte, int location, RangeTblEntry** col_rte);
static TargetEntry* find_column_in_targetlist_by_name(List* targetList, const char* column_name);
static bool check_parent_rte_is_diff(RangeTblEntry* parent_rte, List* parent_rte_list);
void pull_up_expr_varno(Query* parse, RangeTblEntry* rte, SkewColumnInfo* column_info);
static void set_colinfo_by_relation(Oid relid, int location, SkewColumnInfo* column_info, char* column_name);
static void set_colinfo_by_tge(
    TargetEntry* tge, SkewColumnInfo* column_info, char* column_name, RangeTblEntry* rte = NULL, Query* parse = NULL);
static void set_skew_value(PlannerInfo* root, SkewHintTransf* skew_hint_transf, Oid* col_typid);
static bool set_skew_value_to_datum(
    Value* skew_value, Datum* val_datum, bool* constisnull, Oid val_typid, int4 val_typmod, ErrorData** edata);
static int get_col_location(const char* column_name, List* eref_col);
static RangeTblEntry* get_rte(Query* parse, const char* skew_relname);
static char* get_name(ListCell* lc);
static bool support_redistribution(Oid typid);
static List* delete_invalid_hint(PlannerInfo* root, HintState* hint, List* list);
static Relids OuterInnerJoinCreate(Query* parse, HintState* hstate, List* relnames, bool join_order_hint);
static unsigned int get_rewrite_rule_bits(RewriteHint* hint);

extern yyscan_t hint_scanner_init(const char* str, hint_yy_extra_type* yyext);
extern void hint_scanner_destroy(yyscan_t yyscanner);
extern void hint_scanner_yyerror(const char* msg, yyscan_t yyscanner);
extern Datum GetDatumFromString(Oid typeOid, int4 typeMod, char* value);
extern Const* makeConst(Oid consttype, int32 consttypmod, Oid constcollid, int constlen, Datum constvalue,
    bool constisnull, bool constbyval, Cursor_Data* vars = NULL);
extern Numeric int64_to_numeric(int64 v);

static bool IsScanUseDesthint(void* val1, void* val2);

/* Expression kind codes for preprocess_expression */
#define EXPRKIND_QUAL 0
#define EXPRKIND_TARGET 1
#define EXPRKIND_RTFUNC 2
#define EXPRKIND_VALUES 3
#define EXPRKIND_LIMIT 4
#define EXPRKIND_APPINFO 5
#define EXPRKIND_TABLESAMPLE 6

extern Node* preprocess_expression(PlannerInfo* root, Node* expr, int kind);

void yyset_lineno(int line_number, yyscan_t yyscanner);

/* @Description: append space mark into the buffer
 * @inout buf: buffer
 * @in location: the current location
 * @in column_len: number of columns going to write
 */
static void append_space_mark(StringInfo buf, int location, int column_len)
{
    if (location == 0) {
        appendStringInfoCharMacro(buf, '(');
    } else if (column_len > 1) {
        appendStringInfoCharMacro(buf, ')');
        appendStringInfoCharMacro(buf, ' ');
        appendStringInfoCharMacro(buf, '(');
    } else {
        /* For case B, isfirst is always true */
        appendStringInfoCharMacro(buf, ' ');
    }
}

/* @Description: append actual value into the buffer
 * @inout buf: buffer
 * @in value: the Value node going to write
 * @in node: parent node if there is any
 */
static void append_value(StringInfo buf, Value* value, Node* node)
{
    if (IsA(node, Null)) {
        appendStringInfoString(buf, value->val.str);
    } else {
        appendStringInfoCharMacro(buf, '\'');
        appendStringInfoString(buf, value->val.str);
        appendStringInfoCharMacro(buf, '\'');
    }
}

#define HINT_NUM 17
#define HINT_KEYWORD_NUM 23

typedef struct {
    HintKeyword keyword;
    char* keyStr;
} KeywordPair;

const char* G_HINT_KEYWORD[HINT_KEYWORD_NUM] = {
    (char*) HINT_NESTLOOP,
    (char*) HINT_MERGEJOIN,
    (char*) HINT_HASHJOIN,
    (char*) HINT_LEADING,
    (char*) HINT_ROWS,
    (char*) HINT_BROADCAST,
    (char*) HINT_REDISTRIBUTE,
    (char*) HINT_BLOCKNAME,
    (char*) HINT_TABLESCAN,
    (char*) HINT_INDEXSCAN,
    (char*) HINT_INDEXONLYSCAN,
    (char*) HINT_SKEW,
    (char*) HINT_PRED_PUSH,
    (char*) HINT_PRED_PUSH_SAME_LEVEL,
    (char*) HINT_REWRITE,
    (char*) HINT_GATHER,
    (char*) HINT_NO_EXPAND,
    (char*) HINT_SET,
    (char*) HINT_CPLAN,
    (char*) HINT_GPLAN,
    (char*) HINT_SQL_IGNORE,
    (char*) HINT_CHOOSE_ADAPTIVE_GPLAN,
    (char*) HINT_NO_GPC,
};

/*
 * @Description: Describe hint keyword to string
 * @in keyword: hint keyword.
 */
static const char* KeywordDesc(HintKeyword keyword)
{
    const char* value = NULL;
    /* In case new tag is added within the old range. Keep the LFS as the newest keyword */
    Assert(HINT_KEYWORD_NO_GPC == HINT_KEYWORD_NUM - 1);
    if ((int)keyword >= HINT_KEYWORD_NUM || (int)keyword < 0) {
        elog(WARNING, "unrecognized keyword %d", (int)keyword);
    } else {
        value = G_HINT_KEYWORD[(int)keyword];
    }

    return value;
}

/*
 * @Description: Append relnames to buf.
 * @in relnames: Rel name list.
 * @out buf: String buf.
 */
static void relnamesToBuf(List* relnames, StringInfo buf)
{
    bool isfirst = true;
    char* relname = NULL;
    ListCell* lc = NULL;

    Assert(buf != NULL);
    foreach (lc, relnames) {
        Node* node = (Node*)lfirst(lc);
        if (IsA(node, String)) {
            Value* string_value = (Value*)node;
            relname = string_value->val.str;

            if (isfirst) {
                appendStringInfoString(buf, quote_identifier(relname));
                isfirst = false;
            } else {
                appendStringInfoCharMacro(buf, ' ');
                appendStringInfoString(buf, quote_identifier(relname));
            }
        } else if (IsA(node, List)) {
            if (isfirst) {
                appendStringInfoString(buf, "(");
                relnamesToBuf((List*)node, buf);
                appendStringInfoString(buf, ")");
                isfirst = false;
            } else {
                appendStringInfoCharMacro(buf, ' ');
                appendStringInfoString(buf, "(");
                relnamesToBuf((List*)node, buf);
                appendStringInfoString(buf, ")");
            }
        }
    }
}

/*
 * @Description: Collect predpush tables in the same level.
 * @in root: Root of the current SQL.
 * @out result: Relids of the tables which are predpush on.
 */
Relids predpush_candidates_same_level(PlannerInfo *root)
{
    HintState *hstate = root->parse->hintState;
    if (hstate == NULL)
        return NULL;

    if (hstate->predpush_hint == NULL)
        return NULL;

    ListCell *lc = NULL;
    Relids result = NULL;
    foreach (lc, hstate->predpush_hint) {
        PredpushHint *predpushHint = (PredpushHint*)lfirst(lc);
        result = bms_union(result, predpushHint->candidates);
    }

    return result;
}

/*
 * is_predpush_same_level_matched
 *    Check if the predpush samwe level is matched.
 * @param predpush same level hint, relids from baserel, param path info.
 * @param check_dest: don't check dest id when create paths.
 * @return true if matched.
 */
bool is_predpush_same_level_matched(PredpushSameLevelHint* hint, Relids relids, ParamPathInfo* ppi)
{
    if (ppi == NULL) {
        return false;
    }
    if (hint->dest_id == 0 || hint->candidates == NULL) {
        return false;
    }

    if (!bms_is_member(hint->dest_id, relids)) {
        return false;
    }

    if (!bms_equal(ppi->ppi_req_outer, hint->candidates)) {
        return false;
    }
    return true;
}

/*
 * @Description: get the prompts for no_gpc hint into subquery.
 * @in hint: subquery no_gpc hint.
 * @out buf: String buf.
 */
static void NoGPCHintDesc(NoGPCHint* hint, StringInfo buf)
{
    Assert(buf != NULL);

    appendStringInfo(buf, " %s", KeywordDesc(hint->base.hint_keyword));
}

/*
 * @Description: get the prompts for no_expand hint into subquery.
 * @in hint: subquery no_expand hint.
 * @out buf: String buf.
 */
static void NoExpandHintDesc(NoExpandHint* hint, StringInfo buf)
{
    Assert(buf != NULL);

    appendStringInfo(buf, " %s", KeywordDesc(hint->base.hint_keyword));
}

/*
 * @Description: get the prompts for plancache hint into subquery.
 * @in hint: subquery plancache hint.
 * @out buf: String buf.
 */
static void PlanCacheHintDesc(PlanCacheHint* hint, StringInfo buf)
{
    Assert(buf != NULL);

    appendStringInfo(buf, " %s", KeywordDesc(hint->base.hint_keyword));
}

/*
 * @Description: get the prompts for set-guc hint into subquery.
 * @in hint: set-guc hint.
 * @out buf: String buf.
 */
static void SetHintDesc(SetHint* hint, StringInfo buf)
{
    Assert(buf != NULL);

    appendStringInfo(buf, " %s(", KeywordDesc(hint->base.hint_keyword));

    if (hint->name != NULL) {
        appendStringInfo(buf, "%s", hint->name);
    }
    
    if (hint->value != NULL) {
        appendStringInfo(buf, " %s", hint->value);
    }

    appendStringInfoString(buf, ")");
}

/*
 * @Description: get the prompts for redicate pushdown into subquery.
 * @in hint: predicate pushdown hint.
 * @out buf: String buf.
 */
static void PredpushHintDesc(PredpushHint* hint, StringInfo buf)
{
    Hint base_hint = hint->base;

    Assert(buf != NULL);

    appendStringInfo(buf, " %s(", KeywordDesc(hint->base.hint_keyword));

    if (hint->candidates != NULL)
        appendStringInfo(buf, "(");

    relnamesToBuf(base_hint.relnames, buf);

    if (hint->dest_name != NULL) {
        appendStringInfo(buf, ", %s", hint->dest_name);
    }

    if (hint->candidates != NULL)
        appendStringInfo(buf, ")");

    appendStringInfoString(buf, ")");
}

/*
 * @Description: get the prompts for predicate pushdown same level.
 * @in hint: predicate pushdown same level hint.
 * @out buf: String buf.
 */
static void PredpushSameLevelHintDesc(PredpushSameLevelHint* hint, StringInfo buf)
{
    Hint base_hint = hint->base;

    Assert(buf != NULL);

    appendStringInfo(buf, " %s(", KeywordDesc(hint->base.hint_keyword));

    if (hint->candidates != NULL) {
        appendStringInfo(buf, "(");
    }

    relnamesToBuf(base_hint.relnames, buf);
    if (hint->dest_name != NULL) {
        appendStringInfo(buf, ", %s", hint->dest_name);
    }

    if (hint->candidates != NULL) {
        appendStringInfo(buf, ")");
    }

    appendStringInfoString(buf, ")");
}

/*
 * @Description: get the prompts for rewrite hint into subquery.
 * @in hint: rewrite hint.
 * @out buf: String buf.
 */
static void RewriteHintDesc(RewriteHint* hint, StringInfo buf)
{
    Hint base_hint = hint->base;

    Assert(buf != NULL);

    appendStringInfo(buf, " %s(", KeywordDesc(hint->base.hint_keyword));

    relnamesToBuf(hint->param_names, buf);

    appendStringInfoString(buf, ")");
}

/*
 * @Description: get the prompts for redicate pushdown into subquery.
 * @in hint: predicate pushdown hint.
 * @out buf: String buf.
 */
static void GatherHintDesc(GatherHint* hint, StringInfo buf)
{
    Hint base_hint = hint->base;

    Assert(buf != NULL);

    appendStringInfo(buf, " %s(", KeywordDesc(hint->base.hint_keyword));

    if (hint->source == HINT_GATHER_REL) {
        appendStringInfo(buf, "REL");
    } else if (hint->source == HINT_GATHER_JOIN) {
        appendStringInfo(buf, "JOIN");
    } else {
        appendStringInfo(buf, "ALL");
    }

    appendStringInfoString(buf, ")");
}

/*
 * @Description: Describe join hint to string.
 * @in hint: Join hint.
 * @out buf: String buf.
 */
static void JoinMethodHintDesc(JoinMethodHint* hint, StringInfo buf)
{
    Hint base_hint = hint->base;

    Assert(buf != NULL);
    if (hint->negative) {
        appendStringInfo(buf, " %s", HINT_NO);
    }

    appendStringInfo(buf, " %s(", KeywordDesc(hint->base.hint_keyword));

    if (hint->inner_joinrelids != NULL) {
        appendStringInfo(buf, "(");
    }

    relnamesToBuf(base_hint.relnames, buf);

    if (hint->inner_joinrelids != NULL) {
        appendStringInfo(buf, ")");
    }

    appendStringInfoString(buf, ")");
}

/*
 * @Description: Describe leading hint to string.
 * @in hint: Leading hint.
 * @in buf: String buf.
 */
static void LeadingHintDesc(LeadingHint* hint, StringInfo buf)
{
    Hint base_hint = hint->base;

    Assert(buf != NULL);
    appendStringInfo(buf, " %s(", KeywordDesc(hint->base.hint_keyword));

    if (hint->join_order_hint) {
        appendStringInfoString(buf, "(");
    }

    relnamesToBuf(base_hint.relnames, buf);

    if (hint->join_order_hint) {
        appendStringInfoString(buf, ")");
    }

    appendStringInfoString(buf, ")");
}

/*
 * @Description: Describe rows hint to string.
 * @in hint: Rows hint.
 * @in buf: String buf.
 */
static void RowsHintDesc(RowsHint* hint, StringInfo buf)
{
    Hint base_hint = hint->base;

    Assert(buf != NULL);
    appendStringInfo(buf, " %s(", KeywordDesc(hint->base.hint_keyword));

    relnamesToBuf(base_hint.relnames, buf);

    switch (hint->value_type) {
        case RVT_ABSOLUTE:
            appendStringInfoString(buf, " #");
            break;
        case RVT_ADD:
            appendStringInfoString(buf, " +");
            break;
        case RVT_SUB:
            appendStringInfoString(buf, " -");
            break;
        case RVT_MULTI:
            appendStringInfoString(buf, " *");
            break;
        default:
            break;
    }

    if (hint->rows_str != NULL) {
        appendStringInfo(buf, " %s", hint->rows_str);
    } else {
        appendStringInfo(buf, " %.lf", hint->rows);
    }

    appendStringInfoString(buf, ")");
}

/*
 * @Description: Describe stream hint to string.
 * @in hint: Stream hint.
 * @in buf: String buf.
 */
static void StreamHintDesc(StreamHint* hint, StringInfo buf)
{
#ifndef ENABLE_MULTIPLE_NODES
    DISTRIBUTED_FEATURE_NOT_SUPPORTED();
#endif
    Hint base_hint = hint->base;

    Assert(buf != NULL);
    if (hint->negative) {
        appendStringInfo(buf, " %s", HINT_NO);
    }

    appendStringInfo(buf, " %s(", KeywordDesc(hint->base.hint_keyword));
    relnamesToBuf(base_hint.relnames, buf);
    appendStringInfoString(buf, ")");
}

/*
 * @Description: Describe blockname hint to string.
 * @in hint: block hint.
 * @in buf: String buf.
 */
static void BlockNameHintDesc(BlockNameHint* hint, StringInfo buf)
{
    Assert(buf != NULL);

    Hint base_hint = hint->base;
    appendStringInfo(buf, " %s(", KeywordDesc(hint->base.hint_keyword));
    relnamesToBuf(base_hint.relnames, buf);
    appendStringInfoString(buf, ")");
}

/*
 * @Description: Describe scan hint to string.
 * @in hint: Scan hint.
 * @in buf: String buf.
 */
static void ScanMethodHintDesc(ScanMethodHint* hint, StringInfo buf)
{
    Assert(buf != NULL);

    Hint base_hint = hint->base;
    if (hint->negative) {
        appendStringInfo(buf, " %s", HINT_NO);
    }

    appendStringInfo(buf, " %s(", KeywordDesc(hint->base.hint_keyword));
    relnamesToBuf(base_hint.relnames, buf);

    if (hint->indexlist) {
        appendStringInfoCharMacro(buf, ' ');
        relnamesToBuf(hint->indexlist, buf);
    }

    appendStringInfoString(buf, ")");
}

/*
 * @Description: Describe skew hint to string.
 * @in hint: Skew hint.
 * @in buf: String buf.
 */
static void SkewHintDesc(SkewHint* hint, StringInfo buf)
{
    Assert(buf != NULL);

    Hint base_hint = hint->base;
    appendStringInfo(buf, " %s(", KeywordDesc(hint->base.hint_keyword));

    /* Skew hint formats are:
     *  case A: skew(t (c1) (v1))
     *  case B: skew(t (c1) (v1 v2 v3 ...))
     *  case C: skew(t (c1 c2) (v1 v2))
     *  case D: skew(t (c1 c2) ((v1 v2) (v3 v4) (v5 v6) ...))
     *  case F: skew((t1 t3) (c1 c2) ((v1 v2) (v3 v4) (v5 v6) ...))
     */
    if (list_length(hint->base.relnames) == 1) {
        relnamesToBuf(base_hint.relnames, buf);
    } else {
        appendStringInfoCharMacro(buf, '(');
        relnamesToBuf(base_hint.relnames, buf);
        appendStringInfoString(buf, ")");
    }

    if (hint->column_list) {
        appendStringInfoCharMacro(buf, ' ');
        appendStringInfoCharMacro(buf, '(');
        relnamesToBuf(hint->column_list, buf);
        appendStringInfoString(buf, ")");
    }

    if (hint->value_list) {
        int c_length = list_length(hint->column_list);
        int v_length = list_length(hint->value_list);
        ListCell* lc = NULL;
        bool isfirst = true;
        int location = 0;

        appendStringInfoCharMacro(buf, ' ');

        /* For case D */
        if (c_length != 1 && v_length > c_length) {
            appendStringInfoCharMacro(buf, '(');
        }

        foreach (lc, hint->value_list) {
            if (location % c_length == 0) {
                isfirst = true;
            } else {
                isfirst = false;
            }

            /* For null value. */
            Node* node = (Node*)lfirst(lc);
            Value* string_value = (Value*)node;
            char* null_val = "NULL";
            string_value->val.str = IsA(node, Null) ? null_val : string_value->val.str;

            switch (nodeTag(node)) {
                case T_Null:
                case T_BitString:
                case T_String:
                case T_Float: {
                    if (isfirst) {
                        append_space_mark(buf, location, c_length);
                        append_value(buf, string_value, node);
                    } else {
                        appendStringInfoCharMacro(buf, ' ');
                        append_value(buf, string_value, node);
                    }
                    break;
                }
                case T_Integer: {
                    if (isfirst) {
                        append_space_mark(buf, location, c_length);
                        appendStringInfo(buf, "%ld", string_value->val.ival);
                    } else {
                        appendStringInfoCharMacro(buf, ' ');
                        appendStringInfo(buf, "%ld", string_value->val.ival);
                    }
                    break;
                }
                default:
                    break;
            }
            location++;
        }
        if (c_length != 1 && v_length > c_length) {
            appendStringInfoCharMacro(buf, ')');
        }

        appendStringInfoCharMacro(buf, ')');
    }
    appendStringInfoString(buf, ")");
}

/*
 * @Description: Describe hint to string.
 * @in hint: Hint.
 * @in buf: String buf.
 */
char* descHint(Hint* hint)
{
    StringInfoData str;
    initStringInfo(&str);

    switch (hint->type) {
        case T_JoinMethodHint:
            JoinMethodHintDesc((JoinMethodHint*)hint, &str);
            break;
        case T_LeadingHint:
            LeadingHintDesc((LeadingHint*)hint, &str);
            break;
        case T_RowsHint:
            RowsHintDesc((RowsHint*)hint, &str);
            break;
        case T_StreamHint:
            StreamHintDesc((StreamHint*)hint, &str);
            break;
        case T_BlockNameHint:
            BlockNameHintDesc((BlockNameHint*)hint, &str);
            break;
        case T_ScanMethodHint:
            ScanMethodHintDesc((ScanMethodHint*)hint, &str);
            break;
        case T_SkewHint:
            SkewHintDesc((SkewHint*)hint, &str);
            break;
        case T_PredpushHint:
            PredpushHintDesc((PredpushHint*)hint, &str);
            break;
        case T_PredpushSameLevelHint:
            PredpushSameLevelHintDesc((PredpushSameLevelHint*)hint, &str);
            break;
        case T_RewriteHint:
            RewriteHintDesc((RewriteHint*)hint, &str);
            break;
        case T_GatherHint:
            GatherHintDesc((GatherHint*)hint, &str);
            break;
        case T_SetHint:
            SetHintDesc((SetHint*)hint, &str);
            break;
        case T_PlanCacheHint:
            PlanCacheHintDesc((PlanCacheHint*)hint, &str);
            break;
        case T_NoExpandHint:
            NoExpandHintDesc((NoExpandHint*)hint, &str);
            break;
        case T_NoGPCHint:
            NoGPCHintDesc((NoGPCHint*)hint, &str);
            break;
        default:
            break;
    }

    return str.data;
}

/*
 * @Description: Append unused hint string into buf.
 * @in hint_list: Hint list.
 * @out hint_buf: Not used hint buf.
 */
static void find_unused_hint_to_buf(List* hint_list, StringInfo hint_buf)
{
    ListCell* lc = NULL;

    foreach (lc, hint_list) {
        Hint* hint = (Hint*)lfirst(lc);

        if (hint->state == HINT_STATE_NOTUSED) {
            appendStringInfo(hint_buf, "%s", descHint(hint));
        }
    }
}

/*
 * @Description: Append used or not used hint string into buf.
 * @in hstate: Hint State.
 * @out buf: Keep hint string.
 */
void desc_hint_in_state(PlannerInfo* root, HintState* hstate)
{
    StringInfoData str_buf;
    initStringInfo(&str_buf);

    /* Append already used hint to buf, not used hint to str_buf. */
    find_unused_hint_to_buf(hstate->join_hint, &str_buf);
    find_unused_hint_to_buf(hstate->row_hint, &str_buf);
    find_unused_hint_to_buf(hstate->rewrite_hint, &str_buf);
    find_unused_hint_to_buf(hstate->gather_hint, &str_buf);
    find_unused_hint_to_buf(hstate->stream_hint, &str_buf);
    find_unused_hint_to_buf(hstate->scan_hint, &str_buf);
    find_unused_hint_to_buf(hstate->block_name_hint, &str_buf);
    find_unused_hint_to_buf(hstate->set_hint, &str_buf);
    find_unused_hint_to_buf(hstate->no_gpc_hint, &str_buf);
    find_unused_hint_to_buf(hstate->predpush_same_level_hint, &str_buf);

    /* for skew hint */
    ListCell* lc = NULL;
    foreach (lc, hstate->skew_hint) {
        SkewHintTransf* skew_hint_transf = (SkewHintTransf*)lfirst(lc);
        if (skew_hint_transf->before->base.state == HINT_STATE_NOTUSED) {
            appendStringInfo(&str_buf, "%s", descHint((Hint*)skew_hint_transf->before));
        }
    }

    if (strlen(str_buf.data) > 0) {
        StringInfoData str;
        initStringInfo(&str);
        appendStringInfo(&str, "unused hint:%s", str_buf.data);
        root->glob->hint_warning = lappend(root->glob->hint_warning, makeString(str.data));
    }

    pfree_ext(str_buf.data);
}

/*
 * @Description: Delete predicate pushdown, free memory.
 * @in hint: predicate pushdown hint.
 */
static void PredpushHintDelete(PredpushHint* hint)
{
    if (hint == NULL)
        return;

    HINT_FREE_RELNAMES(hint);

    bms_free(hint->candidates);
    pfree_ext(hint);
}

/*
 * @Description: Delete predicate pushdown same level, free memory.
 * @in hint: predicate pushdown same level hint.
 */
static void PredpushSameLevelHintDelete(PredpushSameLevelHint* hint)
{
    if (hint == NULL)
        return;

    HINT_FREE_RELNAMES(hint);

    bms_free(hint->candidates);
    pfree_ext(hint);
}

/*
 * @Description: Delete join method hint, free memory.
 * @in hint: Join hint.
 */
static void JoinMethodHintDelete(JoinMethodHint* hint)
{
    if (hint == NULL) {
        return;
    }

    HINT_FREE_RELNAMES(hint);

    bms_free(hint->joinrelids);
    bms_free(hint->inner_joinrelids);
    pfree_ext(hint);
}

/*
 * @Description: Delete leading hint.
 * @in hint: Leading hint.
 */
static void LeadingHintDelete(LeadingHint* hint)
{
    if (hint == NULL) {
        return;
    }

    HINT_FREE_RELNAMES(hint);
    
    pfree_ext(hint);
}

/*
 * @Description: Delete rows hint.
 * @in hint: Rows Hint.
 */
static void RowsHintDelete(RowsHint* hint)
{
    if (hint == NULL) {
        return;
    }

    HINT_FREE_RELNAMES(hint);

    bms_free(hint->joinrelids);
    pfree_ext(hint);
}

/*
 * @Description: Delete rows hint.
 * @in hint: Rows Hint.
 */
static void RewriteHintDelete(RewriteHint* hint)
{
    if (hint == NULL)
        return;

    HINT_FREE_RELNAMES(hint);

    pfree_ext(hint);
}

/*
 * @Description: Delete rows hint.
 * @in hint: Rows Hint.
 */
static void GatherHintDelete(GatherHint* hint)
{
    if (hint == NULL)
        return;
    pfree_ext(hint);
}

/*
 * @Description: Delete stream hint.
 * @in hint: Stream hint.
 */
static void StreamHintDelete(StreamHint* hint)
{
    if (hint == NULL) {
        return;
    }

    HINT_FREE_RELNAMES(hint);

    bms_free(hint->joinrelids);
    pfree_ext(hint);
}

/*
 * @Description: Delete BlockName hint.
 * @in hint: BlockName hint.
 */
static void BlockNameHintDelete(BlockNameHint* hint)
{
    if (hint == NULL) {
        return;
    }

    HINT_FREE_RELNAMES(hint);

    pfree_ext(hint);
}

/*
 * @Description: Delete scan hint.
 * @in hint: scan hint.
 */
static void ScanMethodHintDelete(ScanMethodHint* hint)
{
    if (hint == NULL) {
        return;
    }

    HINT_FREE_RELNAMES(hint);

    if (hint->indexlist) {
        list_free_deep(hint->indexlist);
    }

    pfree_ext(hint);
}

/*
 * @Description: Delete skew hint.
 * @in hint: skew hint.
 */
static void SkewHintDelete(SkewHint* hint)
{
    if (hint == NULL) {
        return;
    }

    HINT_FREE_RELNAMES(hint);

    if (hint->column_list) {
        list_free_deep(hint->column_list);
    }

    if (hint->value_list) {
        list_free_deep(hint->value_list);
    }

    pfree_ext(hint);
    hint = NULL;
}

/*
 * @Description: Delete skew hint.
 * @in hint: skew hint.
 */
static void SkewHintTransfDelete(SkewHintTransf* hint)
{
    if (hint == NULL) {
        return;
    }

    if (hint->before) {
        SkewHintDelete(hint->before);
        hint->before = NULL;
    }

    if (hint->rel_info_list) {
        list_free_deep(hint->rel_info_list);
        hint->rel_info_list = NIL;
    }

    if (hint->column_info_list) {
        list_free_deep(hint->column_info_list);
        hint->column_info_list = NIL;
    }

    if (hint->value_info_list) {
        list_free_deep(hint->value_info_list);
        hint->value_info_list = NIL;
    }

    pfree_ext(hint);
}

/*
 * @Description: Delete hint, call different delete function according to type.
 * @in hint: Deleted hint.
 */
void hintDelete(Hint* hint)
{
    Assert(hint != NULL);

    switch (nodeTag(hint)) {
        case T_JoinMethodHint:
            JoinMethodHintDelete((JoinMethodHint*)hint);
            break;
        case T_LeadingHint:
            LeadingHintDelete((LeadingHint*)hint);
            break;
        case T_RowsHint:
            RowsHintDelete((RowsHint*)hint);
            break;
        case T_StreamHint:
            StreamHintDelete((StreamHint*)hint);
            break;
        case T_BlockNameHint:
            BlockNameHintDelete((BlockNameHint*)hint);
            break;
        case T_ScanMethodHint:
            ScanMethodHintDelete((ScanMethodHint*)hint);
            break;
        case T_SkewHint:
            SkewHintDelete((SkewHint*)hint);
            break;
        case T_PredpushHint:
            PredpushHintDelete((PredpushHint*)hint);
            break;
        case T_PredpushSameLevelHint:
            PredpushSameLevelHintDelete((PredpushSameLevelHint*)hint);
            break;
        case T_RewriteHint:
            RewriteHintDelete((RewriteHint*)hint);
            break;
        case T_GatherHint:
            GatherHintDelete((GatherHint*)hint);
            break;
        default:
            elog(WARNING, "unrecognized hint method: %d", (int)nodeTag(hint));
            break;
    }
}

/*
 * @Descroption: Delete HintState struct.
 * @void return
 */
void HintStateDelete(HintState* hintState)
{
    if (hintState == NULL) {
        return;
    }

    ListCell* lc = NULL;
    foreach (lc, hintState->join_hint) {
        JoinMethodHint* hint = (JoinMethodHint*)lfirst(lc);
        JoinMethodHintDelete(hint);
    }

    foreach (lc, hintState->leading_hint) {
        LeadingHint* hint = (LeadingHint*)lfirst(lc);
        LeadingHintDelete(hint);
    }

    foreach (lc, hintState->row_hint) {
        RowsHint* hint = (RowsHint*)lfirst(lc);
        RowsHintDelete(hint);
    }

    foreach (lc, hintState->rewrite_hint) {
        RewriteHint* hint = (RewriteHint*)lfirst(lc);
        RewriteHintDelete(hint);
    }

    foreach (lc, hintState->gather_hint) {
        GatherHint* hint = (GatherHint*)lfirst(lc);
        GatherHintDelete(hint);
    }

    foreach (lc, hintState->stream_hint) {
        StreamHint* hint = (StreamHint*)lfirst(lc);
        StreamHintDelete(hint);
    }

    foreach (lc, hintState->block_name_hint) {
        BlockNameHint* hint = (BlockNameHint*)lfirst(lc);
        BlockNameHintDelete(hint);
    }

    foreach (lc, hintState->scan_hint) {
        ScanMethodHint* hint = (ScanMethodHint*)lfirst(lc);
        ScanMethodHintDelete(hint);
    }

    foreach (lc, hintState->skew_hint) {
        SkewHint* hint = (SkewHint*)lfirst(lc);
        SkewHintDelete(hint);
    }

    foreach (lc, hintState->predpush_hint) {
        PredpushHint* hint = (PredpushHint*)lfirst(lc);
        PredpushHintDelete(hint);
    }

    foreach (lc, hintState->predpush_same_level_hint) {
        PredpushSameLevelHint* hint = (PredpushSameLevelHint*)lfirst(lc);
        PredpushSameLevelHintDelete(hint);
    }
}

/*
 * @Descroption: Create HintState struct.
 * @return: HintState struct.
 */
HintState* HintStateCreate()
{
    HintState* hstate = NULL;

    hstate = makeNode(HintState);

    hstate->nall_hints = 0;
    hstate->join_hint = NIL;
    hstate->leading_hint = NIL;
    hstate->row_hint = NIL;
    hstate->stream_hint = NIL;
    hstate->scan_hint = NIL;
    hstate->skew_hint = NIL;
    hstate->hint_warning = NIL;
    hstate->multi_node_hint = false;
    hstate->predpush_hint = NIL;
    hstate->predpush_same_level_hint = NIL;
    hstate->rewrite_hint = NIL;
    hstate->gather_hint = NIL;
    hstate->set_hint = NIL;
    hstate->cache_plan_hint = NIL;
    hstate->no_expand_hint = NIL;
    hstate->sql_ignore_hint = false;
    hstate->from_sql_patch = false;
    return hstate;
}

/*
 * @Description: Get hints from the comment in client-supplied query string.
 * @in comment_str: Comment string.
 * @return: Hint string.
 */
static char* get_hints_from_comment(const char* comment_str)
{
    char* head = NULL;
    int len;
    int comment_len = strlen(comment_str);
    int hint_start_len = strlen(HINT_START);
    int start_position = 0;
    int end_position = 0;
    errno_t rc;

    /* extract query head comment, hint string start with "\*+" */
    if (strncmp(comment_str, HINT_START, hint_start_len) != 0) {
        return NULL;
    }

    /* Find first is not space character. */
    for (start_position = hint_start_len; start_position < comment_len; start_position++) {
        if (comment_str[start_position] == '\n' || (!isspace(comment_str[start_position]))) {
            break;
        }
    }

    /* Find comment termination position. */
    for (end_position = comment_len - 1; end_position >= 0; end_position--) {
        if (comment_str[end_position] == '*') {
            break;
        }
    }

    /* Make a copy of hint. */
    len = end_position - start_position;

    if (len <= 0) {
        return NULL;
    }

    head = (char*)palloc(len + 1);
    rc = memcpy_s(head, len, comment_str + start_position, len);
    securec_check(rc, "\0", "\0");
    head[len] = '\0';

    return head;
}

/*
 * @Description: Delete repeated BlockName hint. If have more than one,
 * they will all be discarded. BlockName hint only can have one in a query block.
 * @in hstate: Hint state.
 */
static void drop_duplicate_blockname_hint(HintState* hstate)
{
    Assert(list_length(hstate->block_name_hint) > 1);
    ListCell* lc = list_head(hstate->block_name_hint);

    foreach (lc, hstate->block_name_hint) {
        if (lc == list_head(hstate->block_name_hint)) {
            continue;
        }

        /*
         * treat all but first one as duplicate hint, as there should
         * be only one block hint
         */
        Hint* hint = (Hint*)lfirst(lc);
        hint->state = HINT_STATE_DUPLICATION;
    }

    hstate->block_name_hint = delete_invalid_hint(NULL, hstate, hstate->block_name_hint);
}

static List* keep_last_hint_cell(List* hintList)
{
    if(list_length(hintList) <= 1) {
        return hintList;
    }
    Node* node = (Node*)copyObject(llast(hintList));
    list_free_deep(hintList);
    return lappend(NIL, node);
}

static void AddJoinHint(HintState* hstate, Hint* hint)
{
    hstate->join_hint = lappend(hstate->join_hint, hint);
}

static void AddLeadingHint(HintState* hstate, Hint* hint)
{
    hstate->leading_hint = lappend(hstate->leading_hint, hint);
}

static void AddRowsHint(HintState* hstate, Hint* hint)
{
    hstate->row_hint = lappend(hstate->row_hint, hint);
}

static void AddStreamHint(HintState* hstate, Hint* hint)
{
    hstate->stream_hint = lappend(hstate->stream_hint, hint);
}

static void AddBlockNameHint(HintState* hstate, Hint* hint)
{
    hstate->block_name_hint = lappend(hstate->block_name_hint, hint);
}

static void AddScanMethodHint(HintState* hstate, Hint* hint)
{
    hstate->scan_hint = lappend(hstate->scan_hint, hint);
}

static void AddSkewHint(HintState* hstate, Hint* hint)
{
    hstate->skew_hint = lappend(hstate->skew_hint, hint);
}

static void AddMultiNodeHint(HintState* hstate, Hint* hint)
{
    hstate->multi_node_hint = true;
}

static void AddPredpushHint(HintState* hstate, Hint* hint)
{
    hstate->predpush_hint = lappend(hstate->predpush_hint, hint);
}

static void AddPredpushSameLevelHint(HintState* hstate, Hint* hint)
{
    hstate->predpush_same_level_hint = lappend(hstate->predpush_same_level_hint, hint);
}

static void AddRewriteHint(HintState* hstate, Hint* hint)
{
    hstate->rewrite_hint = lappend(hstate->rewrite_hint, hint);
}

static void AddGatherHint(HintState* hstate, Hint* hint)
{
    hstate->gather_hint = lappend(hstate->gather_hint, hint);
}

static void AddSetHint(HintState* hstate, Hint* hint)
{
    hstate->set_hint = lappend(hstate->set_hint, hint);
}

static void AddPlanCacheHint(HintState* hstate, Hint* hint)
{
    hstate->cache_plan_hint = lappend(hstate->cache_plan_hint, hint);
}

static void AddNoExpandHint(HintState* hstate, Hint* hint)
{
    /* only keep one no_expand hint for each subquery */
    if (list_length(hstate->no_expand_hint) == 0) {
        hstate->no_expand_hint = lappend(hstate->no_expand_hint, hint);
    }
}

static void AddNoGPCHint(HintState* hstate, Hint* hint)
{
    /* only keep one no_gpc hint for each subquery */
    if (list_length(hstate->no_gpc_hint) == 0) {
        hstate->no_gpc_hint = lappend(hstate->no_gpc_hint, hint);
    }
}

static void AddSqlIgnoreHint(HintState* hstate, Hint* hint)
{
    hstate->sql_ignore_hint = true;
}

typedef void (*AddHintFunc)(HintState*, Hint*);

const AddHintFunc G_HINT_CREATOR[HINT_NUM] = {
    AddJoinHint,
    AddLeadingHint,
    AddRowsHint,
    AddStreamHint,
    AddBlockNameHint,
    AddScanMethodHint,
    AddMultiNodeHint,
    AddPredpushHint,
    AddPredpushSameLevelHint,
    AddSkewHint,
    AddRewriteHint,
    AddGatherHint,
    AddSetHint,
    AddPlanCacheHint,
    AddNoExpandHint,
    AddSqlIgnoreHint,
    AddNoGPCHint,
};

HintState* create_hintstate_worker(const char* hint_str)
{
    HintState* hstate = NULL;

    /* Initilized plan hint variable, which will be set in hint parser */
    u_sess->parser_cxt.hint_list = u_sess->parser_cxt.hint_warning = NIL;

    yyscan_t yyscanner;
    hint_yy_extra_type yyextra;

    /* initialize the flex scanner */
    yyscanner = hint_scanner_init(hint_str, &yyextra);

    yyset_lineno(1, yyscanner);

    /* we will go on whether yyparse is successful or not. */
    (void)yyparse(yyscanner);

    hint_scanner_destroy(yyscanner);

    hstate = HintStateCreate();

    if (u_sess->parser_cxt.hint_list != NULL) {
        ListCell* lc = NULL;
        int firstHintTag = T_JoinMethodHint; /* Do not add hint tag before JoinMethodHint. */
        int lastHintTag = T_NoGPCHint; /* Keep this as the last hint tag in nodes.h. */

        foreach (lc, u_sess->parser_cxt.hint_list) {
            Hint* hint = (Hint*)lfirst(lc);
            if (hint == NULL) {
                continue;
            }
            /* In case new tag is added within the old range. Keep the LFS as the newest keyword */
            Assert(lastHintTag == HINT_NUM - 1 + firstHintTag);
            if (nodeTag(hint) < firstHintTag || nodeTag(hint) > lastHintTag) {
                ereport(ERROR,
                    (errcode(ERRCODE_OPTIMIZER_INCONSISTENT_STATE),
                        (errmsg("[Internal Error]: Invalid hint type %d", (int)nodeTag(hint)),
                         errhint("Do not add new hint tag between %d and %d.", firstHintTag, lastHintTag))));
            }
            if (G_HINT_CREATOR[nodeTag(hint) - firstHintTag] == NULL) {
                ereport(ERROR,
                    (errcode(ERRCODE_OPTIMIZER_INCONSISTENT_STATE),
                        (errmsg("[Internal Error]: Hint add function not initialized %d", (int)nodeTag(hint)))));
            }
            G_HINT_CREATOR[nodeTag(hint) - firstHintTag](hstate, hint);
            hstate->nall_hints++;
        }

        list_free(u_sess->parser_cxt.hint_list);
        u_sess->parser_cxt.hint_list = NIL;
    }
    hstate->hint_warning = u_sess->parser_cxt.hint_warning;
    u_sess->parser_cxt.hint_warning = NIL;

    /* When nothing specified a hint, we free HintState and returns NULL. */
    if (hstate->nall_hints == 0 && hstate->hint_warning == NIL) {
        pfree_ext(hstate);
        hstate = NULL;
    } else {
        /* Delete repeated BlockName hint. */
        if (list_length(hstate->block_name_hint) > 1) {
            drop_duplicate_blockname_hint(hstate);
        }
        /* Only keep the last cplan/gplanhint */
        hstate->cache_plan_hint = keep_last_hint_cell(hstate->cache_plan_hint);
    }

    if (hstate && hstate->hint_warning != NULL) {
        u_sess->parser_cxt.has_hintwarning = true;
    }

    return hstate;
}

/*
 * @Description: Generate hint struct according to hint str.
 * @in hints: Hint string.
 * @return: Hintstate struct.
 */
HintState* create_hintstate(const char* hints)
{
    if (hints == NULL) {
        return NULL;
    }

    char* hint_str = NULL;

    hint_str = get_hints_from_comment(hints);
    if (hint_str == NULL) {
        return NULL;
    }

    HintState* hstate = create_hintstate_worker(hint_str);

    pfree_ext(hint_str);
    return hstate;
}

/*
 * @Description: Return index of relation which matches given aliasname.
 * @in pstate: State information used during parse analysis.
 * @in aliasname: rel name.
 * @return: if not found return 0. If same aliasname was used multiple times in a query,
 * return -1 else return relid.
 */
static int find_relid_aliasname(Query* parse, const char* aliasname, bool find_in_rtable)
{
    ListCell* cell = NULL;
    int i = 1;
    int found = NOTFOUNDRELNAME;
    Relids relids = find_in_rtable ? NULL : get_relids_in_jointree((Node*)parse->jointree, false);

    foreach (cell, parse->rtable) {
        RangeTblEntry* rte = (RangeTblEntry*)lfirst(cell);

        /* skip the relation that doesn't exist in jointree, except skew hint */
        if ((find_in_rtable || bms_is_member(i, relids)) &&
            strncmp(aliasname, rte->eref->aliasname, strlen(aliasname) + 1) == 0) {
            if (found == 0) {
                found = i;
            } else {
                return AMBIGUOUSRELNAME;
            }
        }

        i++;
    }

    return found;
}

/*
 * @Description: Create relnames's relids.
 * @in root: query level info.
 * @in pstate: State information used during parse analysis.
 * @in relnames: relname list.
 * @return: relids.
 */
static Relids create_bms_of_relids(PlannerInfo* root, Query* parse, Hint* hint, List* relnamelist, Relids currelids)
{
    int relid;
    Relids relids = currelids;
    List* relnames = (relnamelist == NIL) ? hint->relnames : relnamelist;

    ListCell* lc = NULL;

    if (IsA(hint, JoinMethodHint) && list_length(relnames) == 1) {
        append_warning_to_list(root, hint, "Error hint:%s requires at least two relations.", hint_string);
        return NULL;
    }

    /* For skew hint we will found relation from parse`s rtable. */
    bool find_in_rtable = false;
    if (IsA(hint, SkewHint) || IsA(hint, PredpushHint) || IsA(hint, PredpushSameLevelHint)) {
        find_in_rtable = true;
	}

    foreach (lc, relnames) {
        Node* item = (Node*)lfirst(lc);

        if (IsA(item, String)) {
            Value* string_value = (Value*)lfirst(lc);
            char* relname = string_value->val.str;

            relid = find_relid_aliasname(parse, relname, find_in_rtable);
            if (relid == NOTFOUNDRELNAME) {
                append_warning_to_list(
                    root, hint, "Error hint:%s, relation name \"%s\" is not found.", hint_string, relname);
                if (currelids == NULL) {
                    bms_free(relids);
                }
                return NULL;
            } else if (AMBIGUOUSRELNAME == relid) {
                append_warning_to_list(
                    root, hint, "Error hint:%s, relation name \"%s\" is ambiguous.", hint_string, relname);
                if (currelids == NULL) {
                    bms_free(relids);
                }
                return NULL;
            } else if (bms_is_member(relid, relids)) {
                append_warning_to_list(
                    root, hint, "Error hint:%s, relation name \"%s\" is duplicated.", hint_string, relname);
                if (currelids == NULL) {
                    bms_free(relids);
                }
                return NULL;
            }
            relids = bms_add_member(relids, relid);
        } else {
            Assert(IsA(item, List));
            Relids subrelids = create_bms_of_relids(root, parse, hint, (List*)item, relids);
            if (subrelids != NULL) {
                relids = subrelids;
            } else if (relids != NULL) {
                if (currelids == NULL) {
                    bms_free(relids);
                }
                relids = NULL;
            }
        }
    }

    return relids;
}

/*
 * @Description: Set accurate relids.
 * @in pstate: Query struct.
 * @in l: Hint list.
 */
static List* set_hint_relids(PlannerInfo* root, Query* parse, List* l)
{
    ListCell* lc = list_head(l);
    ListCell* pre = NULL;
    ListCell* next = NULL;

    /*
     * Create bitmap of relids from alias names for each join method hint.
     * Bitmaps are more handy than strings in join searching.
     */
    while (lc != NULL) {
        Relids relids = NULL;

        next = lnext(lc);

        Hint* hint = (Hint*)lfirst(lc);

        relids = create_bms_of_relids(root, parse, hint);
        if (relids != NULL) {
            switch (nodeTag(hint)) {
                case T_JoinMethodHint:
                    ((JoinMethodHint*)hint)->joinrelids = relids;
                    break;
                case T_RowsHint:
                    ((RowsHint*)hint)->joinrelids = relids;
                    break;
                case T_StreamHint:
                    ((StreamHint*)hint)->joinrelids = relids;
                    break;
                case T_ScanMethodHint:
                    ((ScanMethodHint*)hint)->relid = relids;
                    break;
                case T_SkewHint:
                    ((SkewHint*)hint)->relid = relids;
                    break;
                case T_PredpushHint:
                    ((PredpushHint*)hint)->candidates = relids;
                    break;
                case T_PredpushSameLevelHint:
                    ((PredpushSameLevelHint*)hint)->candidates = relids;
                    break;
                default:
                    break;
            }
            pre = lc;
        } else {
            if (!IsA(hint, PredpushHint) || !((PredpushHint*)hint)->negative) {
                l = list_delete_cell(l, lc, pre);
            }
        }

        lc = next;
    }

    return l;
}

/*
 * @Description: Find scan hint according to relid and hint_key_word.
 * @in hstate: HintState.
 * @in relid: Relids.
 * @in keyWord: Hint key word.
 * @return: Scan hint or Null.
 */
ScanMethodHint* find_scan_hint(HintState* hstate, Relids relid, HintKeyword keyWord)
{
    if (hstate == NULL) {
        return NULL;
    }

    ListCell* l = NULL;

    foreach (l, hstate->scan_hint) {
        ScanMethodHint* scanHint = (ScanMethodHint*)lfirst(l);

        if (scanHint->base.hint_keyword == keyWord && bms_equal(scanHint->relid, relid)) {
            return scanHint;
        }
    }

    return NULL;
}

/*
 * @Descriptoion: Find join hint according to relid and join hint key word.
 * @in hstate: HintState.
 * @in joinrelids: Join relids.
 * @in innerrelids: Inner relids.
 * @in keyWord: Join hint key word.
 * @in leading: if leading hint also considered, default true.
 * @return: Join hint or NULL.
 */
List* find_specific_join_hint(
    HintState* hstate, Relids joinrelids, Relids innerrelids, HintKeyword keyWord, bool leading)
{
    if (hstate == NULL) {
        return NIL;
    }

    JoinMethodHint* hint = NULL;
    ListCell* l = NULL;
    List* hint_list = NIL;

    foreach (l, hstate->join_hint) {
        hint = (JoinMethodHint*)lfirst(l);

        HintKeyword hintKeyword = hint->base.hint_keyword;

        if (bms_equal(joinrelids, hint->joinrelids)) {
            /*
             * Join type should be same, when be called in add_path.
             * Leading hint always should be returned, it need not care join type.
             */
            if (hintKeyword == keyWord || (hintKeyword == HINT_KEYWORD_LEADING && leading)) {
                /* If hint's inner rel exist, here need decide inner rel if same. */
                if (hint->inner_joinrelids) {
                    if (bms_equal(hint->inner_joinrelids, innerrelids)) {
                        hint_list = lappend(hint_list, hint);
                    }
                } else {
                    hint_list = lappend(hint_list, hint);
                }
            }
        }
    }

    return hint_list;
}

/*
 * @Descriptoion: Find scan hint according to relid and scan hint key word.
 * @in hstate: HintState.
 * @in relids: Scan relids.
 * @in keyWord: Scan hint key word.
 * @return: Join hint or NULL.
 */
List* find_specific_scan_hint(HintState* hstate, Relids relids, HintKeyword keyWord)
{
    if (hstate == NULL) {
        return NIL;
    }

    ScanMethodHint* hint = NULL;
    ListCell* l = NULL;
    List* hint_list = NIL;

    foreach (l, hstate->scan_hint) {
        hint = (ScanMethodHint*)lfirst(l);

        HintKeyword hintKeyword = hint->base.hint_keyword;

        if (bms_equal(relids, hint->relid)) {
            if (hintKeyword == keyWord) {
                hint_list = lappend(hint_list, hint);
            }
        }
    }

    return hint_list;
}

/*
 * @Description: According to leading hint, generate join hint.
 * @in hstate: Hint state.
 * @in join_relids: Join relids.
 * @in inner_relids: Inner relids.
 * @in relname_list: Rel name string.
 */
static void leading_to_join_hint(HintState* hstate, Relids join_relids, Relids inner_relids, List* relname_list)
{
    JoinMethodHint* hint = makeNode(JoinMethodHint);
    hint->base.hint_keyword = HINT_KEYWORD_LEADING;
    hint->base.state = HINT_STATE_NOTUSED;
    hint->base.relnames = (List*)copyObject(relname_list);
    hint->joinrelids = bms_copy(join_relids);
    hint->inner_joinrelids = inner_relids;

    hstate->join_hint = lappend(hstate->join_hint, hint);
}

/*
 * @Description: Build join hint according to outer inner rels from leading hint.
 * @in parse: Query struct.
 * @in hstate: hint stats of current query level
 * @in relnames: Leading hint relnames.
 * @in join_order_hint: if join order is hinted
 * @return: Join relids.
 */
static Relids OuterInnerJoinCreate(Query* parse, HintState* hstate, List* relnames, bool join_order_hint)
{
    Relids innerrel_ids = NULL;
    Relids joinrel_ids = NULL;
    Relids rel_ids = NULL;
    ListCell* lc = NULL;
    List* tmp_rel_list = NIL;

    foreach (lc, relnames) {
        Node* item = (Node*)lfirst(lc);

        if (IsA(item, String)) {
            rel_ids = bms_make_singleton(find_relid_aliasname(parse, strVal(item)));
        } else if (IsA(item, List)) {
            /* only apply join order in the outer level, so set to false in inner level */
            rel_ids = OuterInnerJoinCreate(parse, hstate, (List*)item, false);
        }
        joinrel_ids = bms_add_members(joinrel_ids, rel_ids);
        tmp_rel_list = lappend(tmp_rel_list, item);

        /* if join order is required, we should add each join pair in the outer level */
        if (join_order_hint && lc != list_head(relnames)) {
            innerrel_ids = rel_ids;
            leading_to_join_hint(hstate, joinrel_ids, innerrel_ids, tmp_rel_list);
        }
    }

    /* if join order is not required, just add whole rel as a overall hint */
    if (!join_order_hint) {
        leading_to_join_hint(hstate, joinrel_ids, innerrel_ids, relnames);
    }

    list_free(tmp_rel_list);

    return joinrel_ids;
}

/*
 * @Description: Transform leading hint.
 * @in pstate: Query struct.
 * @in hstate: Hint state.
 */
static void transform_leading_hint(PlannerInfo* root, Query* parse, HintState* hstate)
{
    LeadingHint* leading_hint = NULL;
    ListCell* lc = NULL;

    foreach (lc, hstate->leading_hint) {
        leading_hint = (LeadingHint*)lfirst(lc);

        Relids joinrelids = NULL;

        joinrelids = create_bms_of_relids(root, parse, &(leading_hint->base));
        /* Leading hint relation is right. */
        if (joinrelids) {
            /* This leading have not specify inner or outer. */
            (void)OuterInnerJoinCreate(parse, hstate, leading_hint->base.relnames, leading_hint->join_order_hint);
            bms_free(joinrelids);
        }

        /*
         * Leading already be converted and keep in join list. we can delete
         * leading hint list.
         */
        hintDelete((Hint*)leading_hint);
    }

    list_free(hstate->leading_hint);
    hstate->leading_hint = NIL;
}

/*
 * @Description: Delete duplicate and error hint from list.
 * @in list: Hint list.
 * @return: New list.
 */
static List* delete_invalid_hint(PlannerInfo* root, HintState* hint, List* list)
{
    ListCell* pre = NULL;
    ListCell* next = NULL;

    StringInfoData str_buf, warning_buf;
    initStringInfo(&str_buf);
    initStringInfo(&warning_buf);

    ListCell* lc = list_head(list);

    while (lc != NULL) {
        next = lnext(lc);

        Hint* hint = (Hint*)lfirst(lc);

        if (hint->state == HINT_STATE_DUPLICATION) {
            appendStringInfo(&str_buf, "%s", descHint(hint));

            hintDelete(hint);
            list = list_delete_cell(list, lc, pre);
        } else {
            pre = lc;
        }

        lc = next;
    }

    appendStringInfo(&warning_buf, "Duplicated or conflict hint:%s, will be discarded.", str_buf.data);

    /*
     * For blockname hint duplication, we detect it in parser phase, so append the info in hint
     * state, or append the info to planner info
     */
    if (root != NULL) {
        root->glob->hint_warning = lappend(root->glob->hint_warning, makeString(warning_buf.data));
    } else {
        /* In parse phase, we use String struct, since we will CopyObject() later */
        hint->hint_warning = lappend(hint->hint_warning, makeString(warning_buf.data));
    }

    return list;
}

/*
 * @Description: Delete duplicate join hint.
 * @in hstate: Hint state.
 */
static void drop_duplicate_join_hint(PlannerInfo* root, HintState* hstate)
{
    bool hasError = false;
    ListCell* lc = NULL;

    foreach (lc, hstate->join_hint) {
        JoinMethodHint* joinHint = (JoinMethodHint*)lfirst(lc);

        if (joinHint->base.state != HINT_STATE_DUPLICATION) {
            ListCell* lc_next = lnext(lc);

            while (lc_next != NULL) {
                JoinMethodHint* join_hint = (JoinMethodHint*)lfirst(lc_next);

                /* Seperately find duplication for leading and non-leading hint */
                if (joinHint->base.hint_keyword == HINT_KEYWORD_LEADING &&
                    join_hint->base.hint_keyword == HINT_KEYWORD_LEADING) {
                    if (bms_equal(joinHint->joinrelids, join_hint->joinrelids)) {
                        /* We prefer to keep one with inner join rels */
                        if (joinHint->inner_joinrelids == NULL && join_hint->inner_joinrelids != NULL) {
                            joinHint->base.state = HINT_STATE_DUPLICATION;
                            hasError = true;
                            break;
                        }
                        join_hint->base.state = HINT_STATE_DUPLICATION;
                        hasError = true;
                    }
                } else if (joinHint->base.hint_keyword != HINT_KEYWORD_LEADING &&
                           join_hint->base.hint_keyword != HINT_KEYWORD_LEADING) {
                    /* This two hint is the same, need delete one. */
                    if (bms_equal(joinHint->joinrelids, join_hint->joinrelids)) {
                        /* we don't allow same keyword or different positive hint */
                        if (joinHint->base.hint_keyword == join_hint->base.hint_keyword ||
                            (!joinHint->negative && !join_hint->negative)) {
                            join_hint->base.state = HINT_STATE_DUPLICATION;
                            hasError = true;
                        }
                    }
                }

                lc_next = lnext(lc_next);
            }
        }
    }

    if (hasError) {
        hstate->join_hint = delete_invalid_hint(root, hstate, hstate->join_hint);
    }
}

/*
 * @Description: Delete duplicate stream hint.
 * @in hstate: Hint state.
 */
static void drop_duplicate_stream_hint(PlannerInfo* root, HintState* hstate)
{
    bool hasError = false;
    ListCell* lc = NULL;

    foreach (lc, hstate->stream_hint) {
        StreamHint* streamHint = (StreamHint*)lfirst(lc);

        if (streamHint->base.state != HINT_STATE_DUPLICATION) {
            ListCell* lc_next = lnext(lc);

            while (lc_next != NULL) {
                StreamHint* stream_hint = (StreamHint*)lfirst(lc_next);

                /* This two hint is the same, need delete one. */
                if (bms_equal(streamHint->joinrelids, stream_hint->joinrelids)) {
                    /* we don't allow same keyword or different positive hint */
                    if (streamHint->base.hint_keyword == stream_hint->base.hint_keyword ||
                        (!streamHint->negative && !stream_hint->negative)) {
                        stream_hint->base.state = HINT_STATE_DUPLICATION;
                        hasError = true;
                    }
                }

                lc_next = lnext(lc_next);
            }
        }
    }

    if (hasError) {
        hstate->stream_hint = delete_invalid_hint(root, hstate, hstate->stream_hint);
    }
}

/*
 * @Description: Delete duplicate row hint.
 * @in hstate: Hint state.
 */
static void drop_duplicate_row_hint(PlannerInfo* root, HintState* hstate)
{
    bool hasError = false;
    ListCell* lc = NULL;

    foreach (lc, hstate->row_hint) {
        RowsHint* rowHint = (RowsHint*)lfirst(lc);

        if (rowHint->base.state != HINT_STATE_DUPLICATION) {
            ListCell* lc_next = lnext(lc);
            while (lc_next != NULL) {
                RowsHint* row_hint = (RowsHint*)lfirst(lc_next);

                if (bms_equal(rowHint->joinrelids, row_hint->joinrelids)) {
                    row_hint->base.state = HINT_STATE_DUPLICATION;
                    hasError = true;
                }

                lc_next = lnext(lc_next);
            }
        }
    }

    if (hasError) {
        hstate->row_hint = delete_invalid_hint(root, hstate, hstate->row_hint);
    }
}

/*
 * @Description: Delete duplicate predpush hint.
 * @in hstate: Hint state.
 */
static void drop_duplicate_predpush_hint(PlannerInfo* root, HintState* hstate)
{
    bool hasError = false;
    ListCell* lc = NULL;

    foreach (lc, hstate->predpush_hint) {
        PredpushHint* predpushHint = (PredpushHint*)lfirst(lc);

        if (predpushHint->base.state != HINT_STATE_DUPLICATION) {
            ListCell* lc_next = lnext(lc);
            while (lc_next != NULL) {
                PredpushHint* predpush_hint = (PredpushHint*)lfirst(lc_next);

                if (predpushHint->dest_id == predpush_hint->dest_id) {
                    predpush_hint->base.state = HINT_STATE_DUPLICATION;
                    hasError = true;
                }

                lc_next = lnext(lc_next);
            }
        }
    }

    if (hasError) {
        hstate->predpush_hint = delete_invalid_hint(root, hstate, hstate->predpush_hint);
    }
}

/*
 * @Description: Delete duplicate predpush same level hint.
 * @in hstate: Hint state.
 */
static void drop_duplicate_predpush_same_level_hint(PlannerInfo* root, HintState* hstate)
{
    bool hasError = false;
    ListCell* lc = NULL;

    foreach (lc, hstate->predpush_same_level_hint) {
        PredpushSameLevelHint* predpushSameLevelHint = (PredpushSameLevelHint*)lfirst(lc);

        if (predpushSameLevelHint->base.state != HINT_STATE_DUPLICATION) {
            ListCell* lc_next = lnext(lc);
            while (lc_next != NULL) {
                PredpushSameLevelHint* predpush_same_level_hint = (PredpushSameLevelHint*)lfirst(lc_next);

                if (predpushSameLevelHint->dest_id == predpush_same_level_hint->dest_id) {
                    predpush_same_level_hint->base.state = HINT_STATE_DUPLICATION;
                    hasError = true;
                }

                lc_next = lnext(lc_next);
            }
        }
    }

    if (hasError) {
        hstate->predpush_same_level_hint = delete_invalid_hint(root, hstate, hstate->predpush_same_level_hint);
    }
}

/*
 * @Description: Delete duplicate rewrite hint.
 * @in hstate: Hint state.
 */
static void drop_duplicate_rewrite_hint(PlannerInfo* root, HintState* hstate)
{
    bool hasError = false;
    ListCell* lc = NULL;

    foreach (lc, hstate->rewrite_hint) {
        RewriteHint* rewriteHint = (RewriteHint*)lfirst(lc);

        if (rewriteHint->base.state != HINT_STATE_DUPLICATION) {
            ListCell* lc_next = lnext(lc);
            while (lc_next != NULL) {
                RewriteHint* rewrite_hint = (RewriteHint*)lfirst(lc_next);

                List *list_diff = list_difference(rewriteHint->param_names, rewrite_hint->param_names);
                if (list_diff == NIL) {
                    rewrite_hint->base.state = HINT_STATE_DUPLICATION;
                    hasError = true;
                }
                list_free(list_diff);

                lc_next = lnext(lc_next);
            }
        }
    }

    if (hasError) {
        hstate->rewrite_hint = delete_invalid_hint(root, hstate, hstate->rewrite_hint);
    }
}

/*
 * @Description: Delete duplicate gather hint.
 * @in hstate: Hint state.
 */
static void drop_duplicate_gather_hint(PlannerInfo* root, HintState* hstate)
{
    bool hasError = false;
    bool hfirst = true;
    ListCell* lc = NULL;
    if (list_length(hstate->gather_hint) > 1) {
        foreach(lc, hstate->gather_hint) {
            if (hfirst) {
                hfirst = false;
                continue;
            }
            GatherHint* gatherHint = (GatherHint*)lfirst(lc);
            gatherHint->base.state = HINT_STATE_DUPLICATION;
            hasError = true;
        }
        elog(WARNING, "Gather Hint: Multiple Gather Hint found. Execute with the first one instead.");
    }

    if (hasError) {
        hstate->gather_hint = delete_invalid_hint(root, hstate, hstate->gather_hint);
    }
}

/*
 * @Description: Delete duplicate scan hint.
 * @in hstate: Hint state.
 */
static void drop_duplicate_scan_hint(PlannerInfo* root, HintState* hstate)
{
    bool hasError = false;
    ListCell* lc = NULL;

    foreach (lc, hstate->scan_hint) {
        ScanMethodHint* scanHint = (ScanMethodHint*)lfirst(lc);

        if (scanHint->base.state != HINT_STATE_DUPLICATION) {
            ListCell* lc_next = lnext(lc);
            while (lc_next != NULL) {
                ScanMethodHint* scan_hint = (ScanMethodHint*)lfirst(lc_next);

                if (bms_equal(scanHint->relid, scan_hint->relid)) {
                    /* if two hints are same keyword, divide into several cases */
                    if (scanHint->base.hint_keyword == scan_hint->base.hint_keyword) {
                        ScanMethodHint* dup_hint = NULL;

                        /*
                         * If A hint has no index, and B hint has index, we have cases:
                         * positive(A) positive(B) A is duplicate.
                         * positive(A) negative(B) not duplicate.
                         * negative(A) positive(B) B is conflict.
                         * negative(A) negative(B) B is duplicate.
                         */
                        if (scanHint->indexlist == NIL && scan_hint->indexlist != NIL) {
                            if (scanHint->negative) {
                                dup_hint = scan_hint;
                            } else if (!scan_hint->negative) {
                                dup_hint = scanHint;
                            }
                        } else if (scan_hint->indexlist == NIL && scanHint->indexlist != NIL) {
                            if (scan_hint->negative) {
                                dup_hint = scanHint;
                            } else if (!scanHint->negative) {
                                dup_hint = scan_hint;
                            }
                        } else {
                            List* diff = list_difference(scanHint->indexlist, scan_hint->indexlist);
                            if (diff == NIL) {
                                dup_hint = scan_hint;
                            } else {
                                list_free(diff);
                            }
                        }
                        
                        if (dup_hint != NULL) {
                            dup_hint->base.state = HINT_STATE_DUPLICATION;
                            hasError = true;
                            if (dup_hint == scanHint) {
                                break;
                            }
                        }
                    } else if (!scanHint->negative && !scan_hint->negative) {
                        /* if two hints are positive with different keyword, mark latter one as duplicate */
                        scan_hint->base.state = HINT_STATE_DUPLICATION;
                        hasError = true;
                    }
                }

                lc_next = lnext(lc_next);
            }
        }
    }

    if (hasError) {
        hstate->scan_hint = delete_invalid_hint(root, hstate, hstate->scan_hint);
    }
}

/*
 * @Description: Delete duplicate skew hint.
 * @root: query info.
 * @in hstate: Hint state.
 */
static void drop_duplicate_skew_hint(PlannerInfo* root, HintState* hstate)
{
    bool hasError = false;
    ListCell* lc = NULL;

    if (list_length(hstate->skew_hint) < 1) {
        return;
    }

    foreach (lc, hstate->skew_hint) {
        SkewHint* skewHint = (SkewHint*)lfirst(lc);

        if (skewHint->base.state != HINT_STATE_DUPLICATION) {
            ListCell* lc_next = lnext(lc);
            while (lc_next != NULL) {
                SkewHint* skew_hint = (SkewHint*)lfirst(lc_next);

                /* Where two hints have same relations */
                List* diff_rel = list_difference(skewHint->base.relnames, skew_hint->base.relnames);
                if (diff_rel == NIL) {
                    List* diff_col = list_difference(skewHint->column_list, skew_hint->column_list);
                    /* If two hints are same keyword, divide into several cases */
                    if (diff_col == NIL) {
                        List* diff_val = list_difference(skewHint->value_list, skew_hint->value_list);
                        if (diff_val == NIL) {
                            skewHint->base.state = HINT_STATE_DUPLICATION;
                            hasError = true;
                        } else {
                            list_free(diff_val);
                        }
                    } else {
                        list_free(diff_col);
                    }
                }
                lc_next = lnext(lc_next);
            }
        }
    }
    if (hasError) {
        /* If there is duplicated hint, then delete the shorter one. */
        hstate->skew_hint = delete_invalid_hint(root, hstate, hstate->skew_hint);
    }
}

/*
 * @Description: Column type whether can do redistribution, and now we do not support_extended_features.
 * @in typid: Column type oid.
 * @return: ture of false.
 */
static bool support_redistribution(Oid typid)
{
    switch (typid) {
        case INT8OID:
        case INT1OID:
        case INT2OID:
        case INT4OID:
        case NUMERICOID:
        case CHAROID:
        case BPCHAROID:
        case VARCHAROID:
        case NVARCHAR2OID:
        case DATEOID:
        case TIMEOID:
        case TIMESTAMPOID:
        case TIMESTAMPTZOID:
        case INTERVALOID:
        case TIMETZOID:
        case SMALLDATETIMEOID:
        case TEXTOID:
            return true;
        default:
            break;
    }

    return false;
}

/*
 * @Description: Get name from ListCell.
 * @in lc: ListCell stores Value struct..
 * @return: name.
 */
static char* get_name(ListCell* lc)
{
    /* Check ListCell. */
    if (lc == NULL) {
        return NULL;
    }

    Value* val = (Value*)lfirst(lc);
    Assert(nodeTag(val) == T_String);
    char* name = strVal(val);

    return name;
}

/*
 * @Description: Get relation`s RTE.
 * @in parse: Query struct.
 * @in skew_relname: relation name in skew hint.
 * @return: rel`s rte.
 */
static RangeTblEntry* get_rte(Query* parse, const char* skew_relname)
{
    if (parse == NULL) {
        return NULL;
    }

    RangeTblEntry* entry = NULL;
    ListCell* lc = NULL;

    if (parse->rtable == NULL) {
        return NULL;
    }

    foreach (lc, parse->rtable) {
        entry = (RangeTblEntry*)lfirst(lc);
        /* Should use alias in skew first, and alias list maybe null. */
        if (entry->eref != NULL) {
            if (strncmp(entry->eref->aliasname, skew_relname, NAMEDATALEN) == 0) {
                return entry;
            }
        } else if (entry->relname != NULL) {
            /* It means alisa wasn`t used in skew, so find from relname list. */
            if (strncmp(entry->relname, skew_relname, NAMEDATALEN) == 0) {
                return entry;
            }
        } else {
            /* Alias and relname are both NULL at the same time, unexpected. */
            return NULL;
        }
    }

    return NULL;
}

/*
 * @Description: For subquery and CTE, to get the column location in sub targetlist.
 * @in column_name: column name in skew hint.
 * @in l_subquery_eref: subquery`s eref columns list.
 */
static int get_col_location(const char* column_name, List* eref_col)
{
    /* Check rte expanded reference colmun names. */
    if (eref_col == NIL) {
        return -1;
    }

    int location = -1;
    int i = 1;
    ListCell* lc = NULL;
    foreach (lc, eref_col) {
        char* col_name = get_name(lc);

        if (strncmp(column_name, col_name, NAMEDATALEN) == 0) {
            location = i;
            return location;
        }
        i++;
    }
    return location;
}

/* ------------------------------set skew value info---------------------------- */

/*
 * @Description: Transform skew value into datum.
 * @in skew_value: skew value in hint.
 * @in val_typid: column type oid.
 * @in val_typmod: column type mod.
 * @out val_datum: datum after transformed
 * @out constisnull: whether the value is NULL.
 */
static bool set_skew_value_to_datum(
    Value* skew_value, Datum* val_datum, bool* constisnull, Oid val_typid, int4 val_typmod, ErrorData** edata)
{
    bool set = true;
    MemoryContext oldcontext = CurrentMemoryContext;

    /* If something wrong during parsing hint, we only can output warning. */
    PG_TRY();
    {
        switch (nodeTag(skew_value)) {
            /* For null value. */
            case T_Null: {
                *constisnull = true;
                *val_datum = 0;
                break;
            }
            case T_BitString:
            case T_String:
            case T_Float:
                *val_datum = GetDatumFromString(val_typid, val_typmod, strVal(skew_value));
                break;
            case T_Integer: {
                switch (val_typid) {
                    /* 1. Towards to TINYINT */
                    case INT1OID:
                        *val_datum = UInt8GetDatum(intVal(skew_value));
                        break;
                    /* 2. Towards to SMALLINT */
                    case INT2OID:
                        *val_datum = Int16GetDatum(intVal(skew_value));
                        break;
                    /* 3. Towards to INTEGER */
                    case INT4OID:
                        *val_datum = Int32GetDatum(intVal(skew_value));
                        break;
                    /* 4. Towards to BIGINT */
                    case INT8OID:
                        *val_datum = Int64GetDatum(intVal(skew_value));
                        break;
                    /* 5. Towards to NUMERIC./DECIMAL */
                    case NUMERICOID:
                        *val_datum = NumericGetDatum(int64_to_numeric(intVal(skew_value)));
                        break;
                    default: {
                        /* report error */
                        ereport(ERROR,
                            (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
                                (errmsg("Unsupported typeoid: %u for T_Integer value, please add single quota and try "
                                        "again.",
                                    val_typid))));
                    }
                }
                break;
            }
            default:
                break;
        }
    }
    PG_CATCH();
    {
        /* Save error info */
        (void)MemoryContextSwitchTo(oldcontext);
        *edata = CopyErrorData();

        set = false;

        FlushErrorState();
    }
    PG_END_TRY();

    return set;
}

/*
 * @Description:  Make skew value into const. If has error during transforming, then go to the next
 *                      value instead of returning with null value_info_list.
 * @in root: query level info.
 * @in skew_hint_transf: SkewHintTransf node used to store the info after transform.
 * @in col_typid: column type oid array.
 */
static void set_skew_value(PlannerInfo* root, SkewHintTransf* skew_hint_transf, Oid* col_typid)
{
    if (skew_hint_transf->before->value_list == NIL ||
        skew_hint_transf->before->column_list == NIL ||
        list_length(skew_hint_transf->before->value_list) == 0 ||
        list_length(skew_hint_transf->before->column_list) == 0) {
        return;
    }

    if (list_length(skew_hint_transf->before->value_list) >
        MAX_SKEW_NUM * list_length(skew_hint_transf->before->column_list)) {
        append_warning_to_list(root,
            (Hint*)skew_hint_transf->before,
            "Error hint:%s, do not support more than %d skew values for each column.",
            hint_string,
            MAX_SKEW_NUM);
        skew_hint_transf->value_info_list = NIL;
        return;
    }

    if (list_length(skew_hint_transf->before->value_list) % list_length(skew_hint_transf->before->column_list) != 0) {
        skew_hint_transf->value_info_list = NIL;
        append_warning_to_list(root,
            (Hint*)skew_hint_transf->before,
            "Error hint:%s missing value. Please input enough skew values for every column.",
            hint_string);
        return;
    }

    ListCell* lc = NULL;
    List* l = skew_hint_transf->before->column_list;
    uint32 c_length = list_length(l);
    Oid val_typid;
    int4 val_typmod;
    Oid val_collid;
    int2 val_typlen;
    int col_num = 0;
    Datum val_datum = 0;
    Const* val_const = NULL;
    int i = 0;

    foreach (lc, skew_hint_transf->before->value_list) {
        SkewValueInfo* value_info = makeNode(SkewValueInfo);

        /* Get column from skew hint */
        Value* skew_value = (Value*)lfirst(lc);
        bool constbyval = false;
        bool constisnull = false;
        ErrorData* edata = NULL;

        col_num = i % c_length;
        val_typid = col_typid[col_num];

        Type typ = typeidType(val_typid);
        val_typmod = ((Form_pg_type)GETSTRUCT(typ))->typtypmod;
        val_collid = typeTypeCollation(typ);
        val_typlen = typeLen(typ);
        ReleaseSysCache(typ);

        /* 1.Set skew value into datum. */
        bool set = set_skew_value_to_datum(skew_value, &val_datum, &constisnull, val_typid, val_typmod, &edata);

        if (set == false) {
            append_warning_to_list(root,
                (Hint*)skew_hint_transf->before,
                "Error hint:%s fail to convert skew value to datum. Details: \"%s\"",
                hint_string,
                edata->message);

            /* release error state */
            FreeErrorData(edata);

            pfree_ext(value_info);
            list_free_deep(skew_hint_transf->value_info_list);
            skew_hint_transf->value_info_list = NIL;
            return;
        }

        /* Typlen is from pg_type, if typlen >= 0 and typlen <=8,
         * for the type, all the information stored in datum,
         * then COL_IS_ENCODE() is false but constbyval is true.
         */
        if (!COL_IS_ENCODE(val_typid)) {
            constbyval = true;
        }

        /* 2. Make value info into const. */
        val_const = makeConst(val_typid, val_typmod, val_collid, val_typlen, val_datum, constisnull, constbyval);

        /* Append into skew_hint_transf`s value_info_list */
        value_info->support_redis = true;
        value_info->const_value = val_const;
        skew_hint_transf->value_info_list = lappend(skew_hint_transf->value_info_list, value_info);

        /* For next value */
        i++;
    }
}

/* ------------------------------set skew column info---------------------------- */
/*
 * @Description: pull up varno of expr in column info if need.
 * @in parse: parse tree.
 * @in rte: subquery rte that the column belongs to.
 * @in column_info: column info struct.
 * @return: void.
 */
void pull_up_expr_varno(Query* parse, RangeTblEntry* rte, SkewColumnInfo* column_info)
{
    /* Only for the case that subquery pull up. */
    if (!subquery_can_pull_up(rte, parse)) {
        return;
    }

    Var* var = NULL;
    ListCell* lc = NULL;
    int rtoffset = list_length(parse->rtable) - list_length(rte->subquery->rtable);

    Expr* expr = (Expr*)copyObject(column_info->expr);

    List* var_list = pull_var_clause((Node*)expr, PVC_RECURSE_AGGREGATES, PVC_RECURSE_PLACEHOLDERS);

    foreach (lc, var_list) {
        var = (Var*)lfirst(lc);

        var->varno = var->varno + rtoffset;
        var->varnoold = var->varnoold + rtoffset;
    }

    column_info->expr = expr;
}

/*
 * @Description: Set column info by TargetEntry.
 * @in tge: TargetEntry of column.
 * @in column_name: column name.
 * @out column_info: column info after setting.
 */
static void set_colinfo_by_tge(
    TargetEntry* tge, SkewColumnInfo* column_info, char* column_name, RangeTblEntry* rte, Query* parse)
{
    /* Check tge */
    if (tge == NULL) {
        return;
    }

    /* The column may be the func result. */
    Oid typid = exprType((Node*)tge->expr);

    /* Set column info. */
    column_info->relation_Oid = tge->resorigtbl;
    column_info->column_name = column_name;
    column_info->column_typid = typid;
    column_info->expr = (Expr*)tge->expr;

    /* 1.Rte is null means column uses alisa and comes from tlist. */
    if (rte != NULL && !subquery_can_pull_up(rte, parse)) {
        /* If subquery can not be pulled up then column_info->attnum will be set the num in subquery. */
        column_info->attnum = tge->resno;
    } else if (tge->resorigcol) {
        /* 2.If Column comes from base rel then column_info->attnum will be set the num in base rel.
         * Note that subquery that can not be pulled up alse has the tge->resorigcol. So should go step 1.
         */
        column_info->attnum = tge->resorigcol;
    } else {
        /* 3.Column comes from expr. */
        column_info->attnum = tge->resno;
    }

    /* Pull up varno of column expr for the case that subquery be pulled up. */
    if (rte != NULL) {
        pull_up_expr_varno(parse, rte, column_info);
    }
}

/*
 * @Description: Set column info by base relation.
 * @in relid: relation oid that column belongs to.
 * @in location: same as the column attnum in relation.
 * @in column_name: column name.
 * @out column_info: column info after setting.
 */
static void set_colinfo_by_relation(Oid relid, int location, SkewColumnInfo* column_info, char* column_name)
{
    ResourceOwner currentOwner = t_thrd.utils_cxt.CurrentResourceOwner;
    ResourceOwner tmpOwner;
    t_thrd.utils_cxt.CurrentResourceOwner = ResourceOwnerCreate(currentOwner, "ForSkewHint",
        THREAD_GET_MEM_CXT_GROUP(MEMORY_CONTEXT_OPTIMIZER));
    Relation relation = NULL;

    relation = heap_open(relid, AccessShareLock);

    Assert((location + 1) == relation->rd_att->attrs[location].attnum);

    /* Set column info. */
    column_info->relation_Oid = relation->rd_att->attrs[location].attrelid;
    column_info->column_name = column_name;
    column_info->attnum = relation->rd_att->attrs[location].attnum;
    column_info->column_typid = relation->rd_att->attrs[location].atttypid;
    column_info->expr = NULL;

    heap_close(relation, AccessShareLock);

    ResourceOwnerRelease(t_thrd.utils_cxt.CurrentResourceOwner, RESOURCE_RELEASE_BEFORE_LOCKS, true, true);
    ResourceOwnerRelease(t_thrd.utils_cxt.CurrentResourceOwner, RESOURCE_RELEASE_LOCKS, true, true);
    ResourceOwnerRelease(t_thrd.utils_cxt.CurrentResourceOwner, RESOURCE_RELEASE_AFTER_LOCKS, true, true);

    tmpOwner = t_thrd.utils_cxt.CurrentResourceOwner;
    t_thrd.utils_cxt.CurrentResourceOwner = NULL;
    ResourceOwnerDelete(tmpOwner);
    t_thrd.utils_cxt.CurrentResourceOwner = currentOwner;
}

/*
 * @Description: Check the rel whether comes from same subquery.
 * @in parent_rte: subquery rte.
 * @in parent_rte_list: subquery list.
 * @return: Rel comes from same subquery, return false. Otherwise return true.
 */
static bool check_parent_rte_is_diff(RangeTblEntry* parent_rte, List* parent_rte_list)
{
    if (parent_rte_list == NIL) {
        return true;
    }

    /* Rel comes from same subquery. */
    if (list_member(parent_rte_list, parent_rte)) {
        return false;
    } else {
        /* Rel comes from different subquery, and maybe we should count. */
        return true;
    }
}

/*
 * @Description: Find column in tagetlist by name.
 * @in targetList: current parse`s targetList.
 * @in column_relname: column name.
 * @return: tge of the column.
 */
static TargetEntry* find_column_in_targetlist_by_name(List* targetList, const char* column_name)
{
    ListCell* lc = NULL;
    TargetEntry* tge = NULL;
    TargetEntry* tge_tmp = NULL;

    if (targetList == NIL) {
        return NULL;
    }

    foreach (lc, targetList) {
        tge_tmp = (TargetEntry*)lfirst(lc);
        if (tge_tmp == NULL || tge_tmp->resname == NULL) {
            continue;
        } else if (strncmp(column_name, tge_tmp->resname, NAMEDATALEN) == 0) {
            tge = tge_tmp;
            return tge;
        }
    }

    return tge;
}

/*
 * @Description: Find column in targetlist by location in subquery`s column.
 * @in rte: subquery rte.
 * @in location: location in subquery.
 * @return: tge of the column.
 */
static TargetEntry* find_column_in_targetlist_by_location(RangeTblEntry* rte, int location, RangeTblEntry** col_rte)
{
    /* Check the location. */
    if (location < 0 || location >= list_length(rte->subquery->targetList)) {
        return NULL;
    }

    ListCell* lc = list_nth_cell(rte->subquery->targetList, location);
    TargetEntry* tge = (TargetEntry*)lfirst(lc);

    /* For nested subquery.
     * If the subquery can be pull up then we will continue parse.
     * Untill the subquery can not be pull up or the tge is from base rel.
     * Then we get the tge from current subquery`s targetlist
     */
    if (rte->rtekind == RTE_SUBQUERY && !rte->subquery_pull_up) {
        return tge;
    } else {
        /* 1.Tge comes from base rel, then return */
        if (tge->resorigtbl) {
            return tge;
        } else if (!IsA(tge->expr, Var)) {
            /* 2.Tge comes from expr and if the var in expr does not come from other rte kind, then return. */
            List* var_list = pull_var_clause((Node*)tge->expr, PVC_RECURSE_AGGREGATES, PVC_RECURSE_PLACEHOLDERS);
            /* For DFX. */
            foreach (lc, var_list) {
                Var* var = (Var*)lfirst(lc);
                ListCell* lc_rte = list_nth_cell(rte->subquery->rtable, var->varno - 1);
                RangeTblEntry* sub_rte = (RangeTblEntry*)lfirst(lc_rte);

                if (sub_rte->rtekind != RTE_RELATION && sub_rte->rtekind != RTE_SUBQUERY) {
                    *col_rte = sub_rte;
                }
            }
            return tge;
        } else {
            /* 3.Tge comes from subquery, then continue parse. */
            List* var_list = pull_var_clause((Node*)tge->expr, PVC_RECURSE_AGGREGATES, PVC_RECURSE_PLACEHOLDERS);
            if (list_length(var_list) == 1) {
                Var* var = (Var*)linitial(var_list);

                /* Check the var num. */
                Assert(var->varno > 0);
                Assert((unsigned int)list_length(rte->subquery->rtable) >= var->varno);
                if (var->varno < 1 || var->varno > (unsigned int)list_length(rte->subquery->rtable)) {
                    return NULL;
                }

                ListCell* lc = list_nth_cell(rte->subquery->rtable, var->varno - 1);
                RangeTblEntry* sub_rte = (RangeTblEntry*)lfirst(lc);

                *col_rte = sub_rte;

                /* Check the rte_kind, here we only deal with subquery. */
                if (sub_rte->rtekind != RTE_SUBQUERY) {
                    return NULL;
                }

                return find_column_in_targetlist_by_location(sub_rte, location, col_rte);
            }
        }
    }
    return NULL;
}

/*
 * @Description: Find column in subquery for the case that subquery pull up.
 * @in parse: parse tree.
 * @in column_name: column name.
 * @out location: column`s location in subquery.
 * @return: rte that the column belongs to.
 */
static RangeTblEntry* find_column_in_rtable_subquery(Query* parse, const char* column_name, int* location)
{
    ListCell* lc = NULL;
    RangeTblEntry* rte = NULL;
    int location_tmp = -1;

    foreach (lc, parse->rtable) {
        RangeTblEntry* rte_tmp = (RangeTblEntry*)lfirst(lc);

        /* Here only deal with subquery not for base rel. */
        if (rte_tmp->rtekind != RTE_SUBQUERY) {
            continue;
        } else if (subquery_can_pull_up(rte_tmp, parse)) {
            /* For the case that skew hint out of subquery. And the skew column use subquery`s col */
            location_tmp = get_col_location(column_name, rte_tmp->eref->colnames);
            if (location_tmp > -1) {
                *location = location_tmp;
                rte = rte_tmp;
                return rte;
            }

            /* For the case that skew hint in subquery and be pulled up with subquery. And the skew column use alisa in
             * sub tlist */
            TargetEntry* tge_tmp = find_column_in_targetlist_by_name(rte_tmp->subquery->targetList, column_name);
            if (tge_tmp != NULL) {
                *location = tge_tmp->resno;
                rte = rte_tmp;
                return rte;
            }
        } else {
            /* Subquery can not be pulled up.
             * For the case that skew hint out of subquery and subquery can not be pulled up.
             * We also support use column in sub tlist to do hint.
             * e.g. (select a,b from t1... group by 1)tmp(aa,bb), then supporting a\b or aa\bb in hint.
             */
            TargetEntry* tge_tmp = find_column_in_targetlist_by_name(rte_tmp->subquery->targetList, column_name);
            if (tge_tmp != NULL) {
                *location = tge_tmp->resno;
                rte = rte_tmp;
                return rte;
            }
        }
    }

    return rte;
}

/*
 * @Description: Find column from rel_info_list of SkewHintTransf.
 * @in SkewHintTransf: SkewHintTransf node used to store the info after transform.
 * @in column_name: column name.
 * @out count: count the times the find column in rels.
 * @out location: column`s location in rel.
 * @return: rte that the column belongs to.
 */
static RangeTblEntry* find_column_in_rel_info_list(
    SkewHintTransf* skew_hint_transf, const char* column_name, int* count, int* location)
{
    ListCell* lc = NULL;
    RangeTblEntry* rte = NULL;
    int rel_num = 0;
    int location_tmp = -1;

    /* For checking the rel in rel_info_list whether comes from the same subquery. */
    List* parent_rte_list = NIL;

    /* Find column in rel info list. */
    foreach (lc, skew_hint_transf->rel_info_list) {
        SkewRelInfo* rel_info = (SkewRelInfo*)lfirst(lc);

        location_tmp = get_col_location(column_name, rel_info->rte->eref->colnames);
        if (location_tmp > -1) {
            /*
             * Note the case: when subquery pull up,  subquery`s rtable and the current parse`s rtable
             * have same name column. In the case, column should not be ambiguous, e.g.
             * (1)select /+ skew((tp t3) (b) (12)) /... from (select t1.a, t1.b from t1, t2 where t1.a = t2.c)tp(aa,bb),
             * t3 where ...; and t1, t2, t3 all have the same column 'b'. (2)select /+ skew(tp (b) (12)) /... from
             * (select t1.a, t1.b from t1, t2 where t1.a = t2.c)tp(a,b); and t1, t2, tp all have the same column 'b'.
             *
             * We only count for base rel and subquery, not for rel form subquery.
             */
            int location_in_parent = -1;

            /* Rel comes from subquery pull up. */
            if (rel_info->parent_rte) {
                /* Check parent rte expanded reference colmun names. */
                if (!rel_info->parent_rte->eref->colnames) {
                    continue;
                }

                location_in_parent = get_col_location(column_name, rel_info->parent_rte->eref->colnames);
                /* If column is in subquery and the rel comes from different subquery, then count! */
                if (location_in_parent > -1 && check_parent_rte_is_diff(rel_info->parent_rte, parent_rte_list)) {
                    rel_num++;

                    /* Set the rte to parent_rte for go to the function 'find_column_in_targetlist_by_loctation' to set
                     * column info. */
                    rte = rel_info->parent_rte;

                    /* The location in subquery finding in tlist to set the column_attnum. */
                    *location = location_in_parent;

                    parent_rte_list = lappend(parent_rte_list, rel_info->parent_rte);
                }
            } else {
                /* Rel it is subquery or base rel, then count! */
                rel_num++;
                rte = rel_info->rte;
                *location = location_tmp;
            }
        }
    }

    *count = rel_num;

    return rte;
}

/*
 * @Description: Set skew column info and store into column_info_list of skew_hint_transf.
 * @in root: query level info.
 * @in parse: parse tree.
 * @in SkewHintTransf: SkewHintTransf node used to store the info after transform.
 * @out col_typid: column type oid.
 */
static void set_skew_column(PlannerInfo* root, Query* parse, SkewHintTransf* skew_hint_transf, Oid** col_typid)
{
    if (skew_hint_transf->before->column_list == NIL) {
        return;
    }

    ListCell* lc = NULL;
    List* l = skew_hint_transf->before->column_list;
    uint32 c_length = list_length(l);

    *col_typid = (Oid*)palloc0(sizeof(Oid) * c_length);

    Oid* type_oid = *col_typid;

    foreach (lc, l) {
        char* column_name = get_name(lc);
        int count = 0;
        int location = -1;
        TargetEntry* tge = NULL;

        SkewColumnInfo* column_info = makeNode(SkewColumnInfo);

        /* 1. Find column in skew rel: for column original name.
         * (1) find column in rel_info_list.
         *  (1-1)if can find column in more then one rel, then the column is ambiguou.
         * (2) if can not found, try to found in current pasre `s rtable. Mainly for the case that subquery pull up.
         *  (2-1) Note that if has skew hint in subquery and be pull up at the same time,
         *            then should find the column in subquery`s targetlist.
         * (3) if found column in subquery, should according to location to get the subquery`s targetlist tge, and set
         * the column_info; (4) if found column in base rel, open the relation and set the column_info;
         * 2. If still not found, try to found in current parse`s targetlist: for column alias.
         */
        /* 1.(1) Find column in rel_info_list. */
        RangeTblEntry* rte = find_column_in_rel_info_list(skew_hint_transf, column_name, &count, &location);

        if (count > 1) {
            append_warning_to_list(root,
                (Hint*)skew_hint_transf->before,
                "Error hint:%s, reference column \"%s\" in skew hint is ambiguous.",
                hint_string,
                column_name);

            pfree_ext(column_info);
            list_free_deep(skew_hint_transf->column_info_list);
            skew_hint_transf->column_info_list = NIL;
            return;
        }

        if (rte == NULL) {
            /* (2) Find column in current pasre `s rtable. Mainly for the case that subquery pull up. And return
             * subquery */
            rte = find_column_in_rtable_subquery(parse, column_name, &location);
        }

        if (rte != NULL) {
            /* (3) If found column in subquery, should according to location to get the subquery`s targetlist tge. */
            if (rte->rtekind == RTE_SUBQUERY) {
                /* col_rte means the rte that column actually comes from. */
                RangeTblEntry* col_rte = rte;
                tge = find_column_in_targetlist_by_location(rte, location - 1, &col_rte);

                /* Enhance  DFX. */
                if (col_rte->rtekind != RTE_SUBQUERY) {
                    append_warning_to_list(root,
                        (Hint*)skew_hint_transf->before,
                        "Error hint:%s, column \"%s\" comes from rel \"%s\" and rel`s RTEkind is %d, but now only "
                        "support RTE_RELATION and RTE_SUBQUERY.",
                        hint_string,
                        column_name,
                        col_rte->eref->aliasname,
                        col_rte->rtekind);

                    pfree_ext(column_info);
                    list_free_deep(skew_hint_transf->column_info_list);
                    skew_hint_transf->column_info_list = NIL;
                    return;
                } else if (tge == NULL) {
                    append_warning_to_list(root,
                        (Hint*)skew_hint_transf->before,
                        "Error hint:%s, reference column \"%s\" can not be found in subquery.",
                        hint_string,
                        column_name);

                    pfree_ext(column_info);
                    list_free_deep(skew_hint_transf->column_info_list);
                    skew_hint_transf->column_info_list = NIL;
                    return;
                }

                set_colinfo_by_tge(tge, column_info, column_name, rte, parse);
            } else if (rte->rtekind == RTE_RELATION) {
                /* (4) If found column in base rel, open the relation and set the column_info. */
                set_colinfo_by_relation(rte->relid, location - 1, column_info, column_name);
            } else {
                /* Only when we found the column comes from RTE_CTE or other kind of rte, we output the warning. */
                append_warning_to_list(root,
                    (Hint*)skew_hint_transf->before,
                    "Error hint:%s, column \"%s\" comes from rel \"%s\" and rel`s RTEkind is %d, but now only support "
                    "RTE_RELATION and RTE_SUBQUERY.",
                    hint_string,
                    column_name,
                    rte->eref->aliasname,
                    rte->rtekind);

                pfree_ext(column_info);
                list_free_deep(skew_hint_transf->column_info_list);
                skew_hint_transf->column_info_list = NIL;
                return;
            }
        } else {
            /* 2. If still not found, try to found in targetlist. */
            tge = find_column_in_targetlist_by_name(parse->targetList, column_name);
            if (tge == NULL) {
                append_warning_to_list(root,
                    (Hint*)skew_hint_transf->before,
                    "Error hint:%s, reference column \"%s\" in skew hint is not found.",
                    hint_string,
                    column_name);

                pfree_ext(column_info);
                list_free_deep(skew_hint_transf->column_info_list);
                skew_hint_transf->column_info_list = NIL;
                return;
            }

            /* Rte will be null in the case that column use alisa in base rel or subquery. not for rel in subquery. */
            set_colinfo_by_tge(tge, column_info, column_name);
        }

        /* Column type if can support redistribution. */
        if (!support_redistribution(column_info->column_typid)) {
            append_warning_to_list(root,
                (Hint*)skew_hint_transf->before,
                "Error hint:%s, reference column \"%s\"(typeoid: %u) can not support redistribution",
                hint_string,
                column_name,
                column_info->column_typid);

            pfree_ext(column_info);
            list_free_deep(skew_hint_transf->column_info_list);
            skew_hint_transf->column_info_list = NIL;
            return;
        }

        if (column_info->expr != NULL) {
            column_info->expr = (Expr*)preprocess_expression(root, (Node*)column_info->expr, EXPRKIND_TARGET);
        }

        *type_oid = column_info->column_typid;
        skew_hint_transf->column_info_list = lappend(skew_hint_transf->column_info_list, column_info);

        type_oid++;
    }
}

/* -------------------------------set skew rel info----------------------------- */

/*
 * @Description: Judge the subquery whether can be pull up.
 * @in subquery_rte: the RangeTblEntry of subquery.
 * @in parse: Query struct.
 */
static bool subquery_can_pull_up(RangeTblEntry* subquery_rte, Query* parse)
{
    if (subquery_rte == NULL || subquery_rte->subquery == NULL) {
        return false;
    }

    List* sub_rel = subquery_rte->subquery->rtable;
    List* dif_l = NIL;

    dif_l = list_difference(sub_rel, parse->rtable);
    if (dif_l == NULL) {
        return true;
    } else if (subquery_rte->subquery_pull_up) {
        return true;
    } else {
        return false;
    }
}

/*
 * @Description: Set base relations into rel_info_list.
 * @in parse: parse tree.
 * @in rte: rte of base rel.
 * @in skew_hint_transf: SkewHintTransf node used to store the info after transform.
 */
static void set_base_rel(Query* parse, RangeTblEntry* rte, SkewHintTransf* skew_hint_transf)
{
    SkewRelInfo* rel_info = makeNode(SkewRelInfo);

    rel_info->relation_name = rte->relname;
    rel_info->relation_oid = rte->relid;
    rel_info->rte = rte;
    rel_info->parent_rte = NULL;

    skew_hint_transf->rel_info_list = lappend(skew_hint_transf->rel_info_list, rel_info);
}

/*
 * @Description: Set subquery all related relations into rel_info_list.
 * @in parse: parse tree.
 * @in rte: rte of subquery.
 * @in skew_hint_transf: SkewHintTransf node used to store the info after transform.
 */
static void set_subquery_rel(
    Query* parse, RangeTblEntry* rte, SkewHintTransf* skew_hint_transf, RangeTblEntry* parent_rte)
{
    ListCell* lc = NULL;
    RangeTblEntry* sub_rte = NULL;
    bool can_pull_up = false;

    /* Judge that the subquery whether can pull up. */
    can_pull_up = subquery_can_pull_up(rte, parse);
    if (can_pull_up) {
        /* Subquery can pull up, so append all rte of subquery into rel_info_list. */
        foreach (lc, rte->subquery->rtable) {
            sub_rte = (RangeTblEntry*)lfirst(lc);
            /* For nested subquery. */
            if (sub_rte->rtekind == RTE_SUBQUERY) {
                /* Set the subquery relation info. Note that all subquery will be pull up to top level parse. */
                set_subquery_rel(parse, sub_rte, skew_hint_transf, rte);
            } else {
                /* Set the base relation info and parent rte. */
                SkewRelInfo* rel_info = makeNode(SkewRelInfo);
                rel_info->relation_name = sub_rte->relname;
                rel_info->relation_oid = sub_rte->relid;
                rel_info->rte = sub_rte;
                rel_info->parent_rte = rte;

                skew_hint_transf->rel_info_list = lappend(skew_hint_transf->rel_info_list, rel_info);
            }
        }
    } else {
        /* Subquery can not pull up, so only append subquery rte into rel_info_list. */
        SkewRelInfo* rel_info = makeNode(SkewRelInfo);
        rel_info->relation_name = rte->eref->aliasname;
        rel_info->relation_oid = 0;
        rel_info->rte = rte;

        /* If rte comes from subquery, then set the parent. */
        if (parent_rte != NULL) {
            rel_info->parent_rte = parent_rte;
        } else {
            rel_info->parent_rte = NULL;
        }

        skew_hint_transf->rel_info_list = lappend(skew_hint_transf->rel_info_list, rel_info);
    }
}

/*
 * @Description: Set skew relation info and store into rel_info_list of skew_hint_transf.
 * @in root: query level info.
 * @in parse: parse tree.
 * @in SkewHintTransf: SkewHintTransf node used to store the info after transform.
 */
static void set_skew_rel(PlannerInfo* root, Query* parse, SkewHintTransf* skew_hint_transf)
{
    ListCell* lc = NULL;

    foreach (lc, skew_hint_transf->before->base.relnames) {
        char* rel_name = get_name(lc);

        /* Find the skew rel from parse tree rtable by rel name. */
        RangeTblEntry* rte = get_rte(parse, rel_name);

        if (rte == NULL) {
            append_warning_to_list(root,
                (Hint*)skew_hint_transf->before,
                "Error hint:%s, relation name \"%s\" is not found.",
                hint_string,
                rel_name);

            skew_hint_transf->rel_info_list = NIL;
            return;
        } else if (rte->rtekind == RTE_SUBQUERY) {
            /* Set the subquery relation info. */
            set_subquery_rel(parse, rte, skew_hint_transf);
        } else if (rte->rtekind == RTE_RELATION) {
            /* Set the base relation info. */
            set_base_rel(parse, rte, skew_hint_transf);
        } else {
            append_warning_to_list(root,
                (Hint*)skew_hint_transf->before,
                "Error hint:%s, relation \"%s\" RTEkind is %d, but now only support RTE_RELATION and RTE_SUBQUERY when "
                "set rel",
                hint_string,
                rel_name,
                rte->rtekind);

            list_free_deep(skew_hint_transf->rel_info_list);
            skew_hint_transf->rel_info_list = NIL;
            return;
        }
    }
}

/*
 * @Description: Transform only one SkewHint into SkewHintTransf form every time.
 * @in root: query level info.
 * @in parse: parse tree.
 * @in skew_hint: SkewHint struct.
 * @return: SkewHintTransf struct or NULL.
 */
static SkewHintTransf* set_skew_hint(PlannerInfo* root, Query* parse, SkewHint* skew_hint)
{
    SkewHintTransf* skew_hint_transf = makeNode(SkewHintTransf);
    skew_hint_transf->before = skew_hint;

    /* 1. Set relation info. */
    set_skew_rel(root, parse, skew_hint_transf);
    if (skew_hint_transf->rel_info_list == NIL) {
        SkewHintTransfDelete(skew_hint_transf);
        skew_hint_transf = NULL;
    } else {
        /* 2. Set column info. */
        Oid* col_typid = NULL;
        set_skew_column(root, parse, skew_hint_transf, &col_typid);
        if (skew_hint_transf->column_info_list == NIL) {
            SkewHintTransfDelete(skew_hint_transf);
            skew_hint_transf = NULL;
        } else {
            /* 3. Set value info. */
            set_skew_value(root, skew_hint_transf, col_typid);
            if (skew_hint_transf->value_info_list == NIL && skew_hint_transf->before->value_list) {
                SkewHintTransfDelete(skew_hint_transf);
                skew_hint_transf = NULL;
            }
        }
    }

    return skew_hint_transf;
}

/*
 * @Description: Transform skew hint into processible type, including:
 *  transfrom relation name to relation oid
 *  transform column name to column type oid
 *  transform value to corresponding type and make into const
 * @in root: query level info.
 * @in parse: parse tree.
 * @in skew_hint_list: SkewHint list.
 */
static void transform_skew_hint(PlannerInfo* root, Query* parse, List* skew_hint_list)
{
    if (skew_hint_list == NULL) {
        return;
    }

    List* skew_hint_transf_l = NULL;
    ListCell* lc = NULL;
    SkewHintTransf* skew_hint_transf = NULL;

    foreach (lc, skew_hint_list) {
        SkewHint* skew_hint = (SkewHint*)lfirst(lc);
        skew_hint_transf = set_skew_hint(root, parse, skew_hint);
        if (skew_hint_transf == NULL) {
            continue;
        } else {
            skew_hint_transf_l = lappend(skew_hint_transf_l, skew_hint_transf);
        }
    }

    /* Put skew hint transform list into parse. */
    parse->hintState->skew_hint = skew_hint_transf_l;
}

/*
 * Check Predpush hint rely on each other.
 */
static void check_predpush_cycle_hint(PlannerInfo *root,
                                      List *predpush_hint_list,
                                      PredpushHint *predpush_hint)
{
    ListCell *lc = NULL;

    if (bms_num_members(predpush_hint->candidates) != 1) {
        return;
    }

    if (predpush_hint->dest_id == 0) {
        return;
    }

    int cur_dest = predpush_hint->dest_id;
    foreach(lc, predpush_hint_list) {
        PredpushHint *prev_hint = (PredpushHint *)lfirst(lc);

        if (prev_hint == predpush_hint) {
            break;
        }

        if (bms_num_members(prev_hint->candidates) != 1) {
            continue;
        }

        if (prev_hint->dest_id == 0) {
            continue;
        }

        int prev_dest = prev_hint->dest_id;
        if (bms_is_member(prev_dest, predpush_hint->candidates) &&
            bms_is_member(cur_dest, prev_hint->candidates)) {
            append_warning_to_list(
                root, (Hint*)predpush_hint, "Error hint:%s, Predpush cannot rely on each other.", hint_string);
        }
    }

    return;
}

/*
 * @Description: Transform predpush hint into processible type, including:
 *  transfrom subquery name
 * @in root: query level info.
 * @in parse: parse tree.
 * @in predpush_hint_list: predpush hint list.
 */
static void transform_predpush_hint(PlannerInfo* root, Query* parse, List* predpush_hint_list)
{
    if (predpush_hint_list == NULL)
        return;

    ListCell* lc = NULL;
    foreach (lc, predpush_hint_list) {
        PredpushHint* predpush_hint = (PredpushHint*)lfirst(lc);
        if (predpush_hint->dest_name == NULL) {
            continue;
        }

        int relid = find_relid_aliasname(parse, predpush_hint->dest_name, true);
        if (relid == NOTFOUNDRELNAME) {
            append_warning_to_list(root, (Hint *)predpush_hint, "Error hint:%s, relation name \"%s\" is not found.",
                                   hint_string, predpush_hint->dest_name);
            continue;
        } else if (relid == AMBIGUOUSRELNAME) {
            append_warning_to_list(root, (Hint *)predpush_hint, "Error hint:%s, relation name \"%s\" is ambiguous.",
                                   hint_string, predpush_hint->dest_name);
            continue;
        }

        predpush_hint->dest_id = relid;
        check_predpush_cycle_hint(root, predpush_hint_list, predpush_hint);
    }

    return;
}

/*
 * @Description: Transform predpush same level hint into processible type, including:
 *  transfrom destination name
 * @in root: query level info.
 * @in parse: parse tree.
 * @in predpush_join_hint_list: predpush same level hint list.
 */
static void transform_predpush_same_level_hint(PlannerInfo* root, Query* parse, List* predpush_same_level_hint_list)
{
    if (predpush_same_level_hint_list == NIL) {
        return;
    }

    ListCell* lc = NULL;
    foreach (lc, predpush_same_level_hint_list) {
        PredpushSameLevelHint* predpush_same_level_hint = (PredpushSameLevelHint*)lfirst(lc);
        if (predpush_same_level_hint->dest_name == NULL) {
            continue;
        }

        int relid = find_relid_aliasname(parse, predpush_same_level_hint->dest_name, true);
        if (relid <= NOTFOUNDRELNAME) {
            continue;
        }

        predpush_same_level_hint->dest_id = relid;
    }

    return;
}

/*
 * @Description: Transform rewrite hint into bitmap.
 * @in root: query level info.
 * @in parse: parse tree.
 * @in rewrite_hint_list: Rewrite Hint list.
 */
static void transform_rewrite_hint(PlannerInfo* root, Query* parse, List* rewrite_hint_list)
{
    if (rewrite_hint_list == NULL)
        return;
    
    ListCell* lc = NULL;
    foreach (lc, rewrite_hint_list) {
        RewriteHint* rewrite_hint = (RewriteHint*)lfirst(lc);
        if (rewrite_hint->param_names == NULL) {
            continue;
        }

        rewrite_hint->param_bits = get_rewrite_rule_bits(rewrite_hint);
    }
}

static unsigned int get_rewrite_rule_bits(RewriteHint* hint)
{
    ListCell* lc = NULL;
    unsigned int bits = 0;

    foreach (lc, hint->param_names)
    {
        Node *param_name_str = (Node*)lfirst(lc);
        if (param_name_str == NULL) {
            continue;
        }
        if (!IsA(param_name_str, String)) {
            continue;
        }
        char* param_name = ((Value*)param_name_str)->val.str;

        if (pg_strcasecmp(param_name, "lazyagg") == 0) {
            bits = bits | LAZY_AGG;
        }
        else if (pg_strcasecmp(param_name, "magicset") == 0) {
            bits = bits | MAGIC_SET;
        }
        else if (pg_strcasecmp(param_name, "partialpush") == 0) {
            bits = bits | PARTIAL_PUSH;
        }
        else if (pg_strcasecmp(param_name, "uniquecheck") == 0) {
            bits = bits | SUBLINK_PULLUP_WITH_UNIQUE_CHECK;
        }
        else if (pg_strcasecmp(param_name, "disablerep") == 0) {
            bits = bits | SUBLINK_PULLUP_DISABLE_REPLICATED;
        }
        else if (pg_strcasecmp(param_name, "intargetlist") == 0) {
            bits = bits | SUBLINK_PULLUP_IN_TARGETLIST;
        }
        else if (pg_strcasecmp(param_name, "disable_pullup_expr_sublink") == 0) {
            bits = bits | SUBLINK_PULLUP_DISABLE_EXPR;
        } else if (pg_strcasecmp(param_name, "enable_sublink_pullup_enhanced") == 0) {
            bits = bits | SUBLINK_PULLUP_ENHANCED;
        } else {
            elog(WARNING, "invalid rewrite rule. (Supported rules: lazyagg, magicset, partialpush, uniquecheck, "
                          "disablerep, intargetlist,disable_pullup_expr_sublink, enable_sublink_pullup_enhanced)");
        }
    }

    return bits;
}


/*
 * @Description: Check rewrite rule constraints hint:
 * @in root: Planner Info
 * @in params: Rewrite rule parameter
 */
bool permit_from_rewrite_hint(PlannerInfo *root, unsigned int params)
{
    unsigned int bits = 0;

    HintState *hstate = root->parse->hintState;
    if (hstate == NULL)
        return true;

    ListCell* lc = NULL;
    foreach (lc, hstate->rewrite_hint) {
        RewriteHint* rewrite_hint = (RewriteHint*)lfirst(lc);

        if (rewrite_hint->param_bits == 0)
            bits = get_rewrite_rule_bits(rewrite_hint);
        else 
            bits = rewrite_hint->param_bits;

        if (bits & params) {
            rewrite_hint->base.state = HINT_STATE_USED;
            return false;
        }
    }
    return true;
}

/*
 * @Description: Check gather constraints hint:
 * @in root: Planner Info
 * @in src: Gather source
 */
bool permit_gather(PlannerInfo *root, GatherSource src)
{
    HintState *hstate = root->parse->hintState;

    /* if null just return */
    if (hstate == NULL || hstate->gather_hint == NULL) {
        return false;
    }

    GatherHint* gather_hint = (GatherHint*)linitial(hstate->gather_hint);
    gather_hint->base.state = HINT_STATE_USED;

    /* if UNKNOWN just return */
    if (src > HINT_GATHER_ALL) {
        return false;
    }

    /*
     * High-level gather hints also enables low-level gather hints
     *      permit_gather(root, HINT_GATHER_REL) versus GATHER(JOIN) will permit
     *      permit_gather(root, HINT_GATHER_JOIN) versus GATHER(JOIN) will permit
     *      permit_gather(root, HINT_GATHER_JOIN) versus GATHER(REL) will abort
     *      permit_gather(root) will used as guc control cn gather feature
     */
    return (src <= gather_hint->source);
}

/*
 * @Description: Get gather hint source:
 * @in root: Planner Info
 */
GatherSource get_gather_hint_source(PlannerInfo *root) {
    HintState *hstate = root->parse->hintState;

    if (hstate == NULL || hstate->gather_hint == NULL) {
        return HINT_GATHER_UNKNOWN;   /* if null */
    }

    GatherHint* gather_hint = (GatherHint*)linitial(hstate->gather_hint);

    return gather_hint->source;
}

/*
 * @Description: Transform hint into handy form.
 *  create bitmap of relids from alias names, to make it easier to check
 *  whether a join path matches a join method hint.
 *  add join method hints which are necessary to enforce join order
 *  specified by Leading hint
 * @in root: query level info.
 * @in parse: Query struct.
 * @in hstate: hint state.
 */
void transform_hints(PlannerInfo* root, Query* parse, HintState* hstate)
{
    if (hstate == NULL) {
        return;
    }

    hstate->join_hint = set_hint_relids(root, parse, hstate->join_hint);
    hstate->row_hint = set_hint_relids(root, parse, hstate->row_hint);
    hstate->stream_hint = set_hint_relids(root, parse, hstate->stream_hint);
    hstate->scan_hint = set_hint_relids(root, parse, hstate->scan_hint);
    hstate->skew_hint = set_hint_relids(root, parse, hstate->skew_hint);
    hstate->predpush_hint = set_hint_relids(root, parse, hstate->predpush_hint);
    hstate->predpush_same_level_hint = set_hint_relids(root, parse, hstate->predpush_same_level_hint);

    transform_leading_hint(root, parse, hstate);

    /* Transform predpush hint, for subquery name */
    transform_predpush_hint(root, parse, hstate->predpush_hint);
    transform_predpush_same_level_hint(root, parse, hstate->predpush_same_level_hint);

    transform_rewrite_hint(root, parse, hstate->rewrite_hint);

    /* Delete duplicate hint. */
    drop_duplicate_join_hint(root, hstate);
    drop_duplicate_stream_hint(root, hstate);
    drop_duplicate_row_hint(root, hstate);
    drop_duplicate_scan_hint(root, hstate);
    drop_duplicate_skew_hint(root, hstate);
    drop_duplicate_predpush_hint(root, hstate);
    drop_duplicate_predpush_same_level_hint(root, hstate);
    drop_duplicate_rewrite_hint(root, hstate);
    drop_duplicate_gather_hint(root, hstate);

    /* Transform skew hint into handy form, SkewHint structure will be transform to SkewHintTransf. */
    transform_skew_hint(root, parse, hstate->skew_hint);
}

/*
 * @Description: check validity of scan hint
 * @in root: planner info of current query level
 */
void check_scan_hint_validity(PlannerInfo* root)
{
    HintState* hintstate = root->parse->hintState;
    if (hintstate == NULL) {
        return;
    }

    ListCell* lc = list_head(hintstate->scan_hint);
    ListCell* pre = NULL;
    ListCell* next = NULL;

    while (lc != NULL) {
        ScanMethodHint* scanHint = (ScanMethodHint*)lfirst(lc);
        bool deleted = false;
        next = lnext(lc);

        /* check if there's more index specified */
        if (list_length(scanHint->indexlist) > 1) {
            append_warning_to_list(
                root, (Hint*)scanHint, "Error hint:%s, only one index can be specified in scan hint.", hint_string);
            deleted = true;
        } else if (scanHint->indexlist != NIL) {
            /* check if index exists for specified table */
            Relids relids = bms_copy(scanHint->relid);
            int relindex = bms_first_member(relids);
            RelOptInfo* rel = root->simple_rel_array[relindex];
            char* hint_index_name = strVal(linitial(scanHint->indexlist));
            ListCell* lc2 = NULL;
            foreach (lc2, rel->indexlist) {
                IndexOptInfo* info = (IndexOptInfo*)lfirst(lc2);
                char* index_name = get_rel_name(info->indexoid);

                if (index_name && strncmp(hint_index_name, index_name, strlen(index_name) + 1) == 0) {
                    break;
                }
            }
            if (lc2 == NULL) {
                append_warning_to_list(
                    root, (Hint*)scanHint, "Error hint:%s, index \"%s\" doesn't exist.", hint_string, hint_index_name);
                deleted = true;
            }
            pfree_ext(relids);
        }
        if (deleted) {
            hintDelete((Hint*)scanHint);
            root->parse->hintState->scan_hint = list_delete_cell(root->parse->hintState->scan_hint, lc, pre);
        } else {
            pre = lc;
        }

        lc = next;
    }
}

/*
 * @Description: Change scan rel id in scan hint of state, used in hdfs case.
 * @in hstate: Hint State.
 * @in oldIdx, newIdx: old and new table entry index.
 */
void adjust_scanhint_relid(HintState* hstate, Index oldIdx, Index newIdx)
{
    if (hstate == NULL) {
        return;
    }

    ListCell* lc = NULL;
    foreach (lc, hstate->scan_hint) {
        ScanMethodHint* scan_hint = (ScanMethodHint*)lfirst(lc);
        if (bms_is_member(oldIdx, scan_hint->relid)) {
            bms_free(scan_hint->relid);
            scan_hint->relid = bms_make_singleton(newIdx);
        }
    }
}

bool pull_hint_warning_walker(Node* node, pull_hint_warning_context* context)
{
    if (node == NULL) {
        return false;
    }

    if (IsA(node, Query)) {
        HintState* hstate = ((Query*)node)->hintState;
        if (hstate != NULL) {
            ListCell* lc = NULL;
            foreach (lc, hstate->hint_warning) {
                Value* v = (Value*)lfirst(lc);
                context->warning = lappend(context->warning, copyObject(v));
            }
            hstate->hint_warning = NIL;
        }
        return query_tree_walker((Query*)node, (bool (*)())pull_hint_warning_walker, (void*)context, QTW_IGNORE_DUMMY);
    }
    return expression_tree_walker(node, (bool (*)())pull_hint_warning_walker, (void*)context);
}

List* retrieve_query_hint_warning(Node* parse)
{
    pull_hint_warning_context context;
    context.warning = NIL;
    (void)pull_hint_warning_walker(parse, &context);
    return context.warning;
}

void output_utility_hint_warning(Node* query, int lev)
{
    List* warning = NIL;

    switch (nodeTag(query)) {
        case T_ViewStmt:
            warning = retrieve_query_hint_warning(((ViewStmt*)query)->query);
            break;
        case T_PrepareStmt:
            warning = retrieve_query_hint_warning(((PrepareStmt*)query)->query);
            break;
        case T_DeclareCursorStmt:
            warning = retrieve_query_hint_warning(((DeclareCursorStmt*)query)->query);
            break;
        case T_RuleStmt:
            /* Need to add after RULE is out of black list */
        default:
            break;
    }
    output_hint_warning(warning, lev);
}

/*
 * @Description: Append used or not used hint string into buf.
 * @in hstate: Hint State.
 * @out buf: Keep hint string.
 */
void output_hint_warning(List* warning, int lev)
{
    ListCell* lc = NULL;

    foreach (lc, warning) {
        Value* msg = (Value*)lfirst(lc);
        ereport(lev, (errmodule(MOD_PLANHINT), errmsg("%s", strVal(msg))));
    }

    list_free_deep(warning);
    warning = NIL;
}

/*
 * enable predpush? we assume that 'No predpush' need to be the first element.
 */
bool permit_predpush(PlannerInfo *root)
{
    if (root == NULL) {
        return true;
    }

    HintState *hstate = root->parse->hintState;
    if (hstate == NULL)
        return true;

    if (hstate->predpush_hint == NULL)
        return true;

    PredpushHint *predpushHint = (PredpushHint*)linitial(hstate->predpush_hint);
    return !predpushHint->negative;
}

const char* G_SET_HINT_WHITE_LIST[] = {
    /* keep in the ascending alphabetical order of frequency */
    (char*)"best_agg_plan",
    (char*)"cost_weight_index",
    (char*)"cpu_index_tuple_cost",
    (char*)"cpu_operator_cost",
    (char*)"cpu_tuple_cost",
    (char*)"default_limit_rows",
    (char*)"effective_cache_size",
    (char*)"enable_bitmapscan",
    (char*)"enable_broadcast",
    (char*)"enable_fast_query_shipping",
    (char*)"enable_functional_dependency",
    (char*)"enable_hashagg",
    (char*)"enable_hashjoin",
    (char*)"enable_index_nestloop",
    (char*)"enable_indexonlyscan",
    (char*)"enable_indexscan",
    (char*)"enable_inner_unique_opt",
    (char*)"enable_material",
    (char*)"enable_mergejoin",
    (char*)"enable_nestloop",
    (char*)"enable_remotegroup",
    (char*)"enable_remotejoin",
    (char*)"enable_remotelimit",
    (char*)"enable_remotesort",
    (char*)"enable_seqscan",
    (char*)"enable_sort",
    (char*)"enable_sortgroup_agg",
    (char*)"enable_stream_operator",
    (char*)"enable_stream_recursive",
    (char*)"enable_tidscan",
    (char*)"enable_trigger_shipping",
    (char*)"node_name",
    (char*)"partition_iterator_elimination",
    (char*)"partition_page_estimation",
    (char*)"query_dop",
    (char*)"random_page_cost",
    (char*)"rewrite_rule",
    (char*)"seq_page_cost",
    (char*)"try_vector_engine_strategy",
    (char*)"var_eq_const_selectivity"};

const unsigned int G_NUM_SET_HINT_WHITE_LIST = sizeof(G_SET_HINT_WHITE_LIST) / sizeof(G_SET_HINT_WHITE_LIST[0]);

static int param_str_cmp(const void *s1, const void *s2)
{
    const char *key = (const char *)s1;
    const char * const *arg = (const char * const *)s2;
    return pg_strcasecmp(key, *arg);
}

bool check_set_hint_in_white_list(const char* name)
{
    if (name == NULL) {
        return false;
    }
    char* res = (char*)bsearch((void *) name,
                               (void *) G_SET_HINT_WHITE_LIST,
                               G_NUM_SET_HINT_WHITE_LIST,
                               sizeof(char*),
                               param_str_cmp);
    return res != NULL;
}

bool has_no_expand_hint(Query* subquery)
{
    if (subquery->hintState == NULL) {
        return false;
    }
    if (subquery->hintState->no_expand_hint != NIL) {
        NoExpandHint* hint = (NoExpandHint*)linitial(subquery->hintState->no_expand_hint);
        hint->base.state = HINT_STATE_USED;
        return true;
    }
    return false;
}

bool has_no_gpc_hint(HintState* hintState)
{
    if (hintState == NULL) {
        return false;
    }
    if (hintState->no_gpc_hint != NIL) {
        NoGPCHint* hint = (NoGPCHint*)linitial(hintState->no_gpc_hint);
        hint->base.state = HINT_STATE_USED;
        return true;
    }
    return false;
}

/*
 * check if is dest hinttype, it's used by function list_cell_clear
 * val1: ScanMethodHint
 * val2: HintKeyword
 */
static bool IsScanUseDesthint(void* val1, void* val2)
{
    ScanMethodHint *scanmethod = (ScanMethodHint*)lfirst((ListCell*)val1);
    HintKeyword* desthint = (HintKeyword*)val2;

    if (scanmethod == NULL) {
        return false;
    }

    if (scanmethod->base.hint_keyword == *desthint) {
        return true;
    } else {
        return false;
    }
}

void RemoveQueryHintByType(Query *query, HintKeyword hint)
{
    if (query->hintState && query->hintState->scan_hint) {
        query->hintState->scan_hint = list_cell_clear(query->hintState->scan_hint, &hint, IsScanUseDesthint);
    }
}

bool CheckNodeNameHint(HintState* hintstate)
{
    if (hintstate == NULL) {
        return false;
    }
    ListCell* lc = NULL;
    foreach (lc, hintstate->set_hint) {
        SetHint* hint = (SetHint*)lfirst(lc);
        if (unlikely(strcmp(hint->name, "node_name") == 0)) {
            return true;
        }
    }
    return false;
}