Files
openGauss-server/src/common/backend/parser/parse_hint.cpp
2023-02-27 04:05:03 -08:00

3954 lines
123 KiB
C++
Executable File

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