/* * 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); } 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 unsigned int G_NUM_SET_HINT_WHITE_LIST = 38; const char* G_SET_HINT_WHITE_LIST[G_NUM_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_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_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"}; 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; }