OmniSciDB  c1a53651b2
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Friends Macros Groups Pages
FromTableReordering.cpp
Go to the documentation of this file.
1 /*
2  * Copyright 2022 HEAVY.AI, Inc.
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  * http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 #include "FromTableReordering.h"
18 #include "Execute.h"
19 #include "ExpressionRewrite.h"
20 #include "RangeTableIndexVisitor.h"
22 
23 #include <numeric>
24 #include <queue>
25 #include <regex>
26 
27 namespace {
28 
29 using cost_t = unsigned;
30 using node_t = size_t;
31 
32 static std::unordered_map<SQLTypes, cost_t> GEO_TYPE_COSTS{{kPOINT, 60},
33  {kARRAY, 60},
34  {kLINESTRING, 70},
35  {kPOLYGON, 80},
36  {kMULTIPOLYGON, 90}};
37 
38 // Returns a lhs/rhs cost for the given qualifier. Must be strictly greater than 0.
39 // todo (yoonmin): compute the cost of inner join edge and outer join edge
40 // Currently, we set 100 for inner join and 200 for loop join
41 // for geometries, we use types of geometries if corresponding qual is not supported
42 // by overlaps hash join framework
43 // otherwise, we take a different approach depending on overlaps join function to maximize
44 // the chance of using the overlaps hash join framework
45 std::tuple<cost_t, cost_t, InnerQualDecision> get_join_qual_cost(
46  const Analyzer::Expr* qual,
47  const Executor* executor) {
48  if (executor) {
49  GeospatialFunctionFinder geo_func_finder;
50  geo_func_finder.visit(qual);
51  shared::TableKey inner_table_key{-1, -1};
52  shared::TableKey outer_table_key{-1, -1};
53  std::tie(inner_table_key, outer_table_key) = geo_func_finder.getTableIdsOfGeoExpr();
54  if (inner_table_key.table_id != -1 && outer_table_key.table_id != -1) {
55  // try to find a chance to swap tables in the binary join
56  // note that self-join does not need to be swapped
57  CHECK_NE(outer_table_key, inner_table_key);
58  const auto inner_table_metadata =
60  const auto outer_table_metadata =
62  const auto& target_geo_func_name = geo_func_finder.getGeoFunctionName();
63  if (inner_table_metadata->fragmenter && outer_table_metadata->fragmenter) {
64  const auto inner_table_cardinality =
65  inner_table_metadata->fragmenter->getNumRows();
66  const auto outer_table_cardinality =
67  outer_table_metadata->fragmenter->getNumRows();
68  auto inner_qual_decision = inner_table_cardinality > outer_table_cardinality
71  // detect the case when table reordering by cardinality incurs unexpected overhead
72  // i.e., SELECT ... FROM R, S where ST_Interesects(S.poly, R.pt) where |R| > |S|
73  // but |R| is not that larger than |S|, i.e., |R| / |S| < 10.0
74  // in this case, it might be a better when keeping the existing ordering
75  // to exploit (overlaps) hash join instead of loop join
76  const auto& geo_args = geo_func_finder.getGeoArgCvs();
77  const auto inner_cv_it =
78  std::find_if(geo_args.begin(),
79  geo_args.end(),
80  [&inner_table_key](const Analyzer::ColumnVar* cv) {
81  return cv->getTableKey() == inner_table_key;
82  });
83  CHECK(inner_cv_it != geo_args.end());
84  const auto outer_cv_it =
85  std::find_if(geo_args.begin(),
86  geo_args.end(),
87  [outer_table_key](const Analyzer::ColumnVar* cv) {
88  return cv->getTableKey() == outer_table_key;
89  });
90  CHECK(outer_cv_it != geo_args.end());
91  const auto inner_cv = *inner_cv_it;
92  bool needs_table_reordering = inner_table_cardinality < outer_table_cardinality;
93  const auto outer_inner_card_ratio =
94  outer_table_cardinality / static_cast<double>(inner_table_cardinality);
96  target_geo_func_name) ||
98  target_geo_func_name)) {
99  // the goal of this is to maximize the chance of using overlaps hash join
100  // to achieve this, point column has zero for its rte_idx (so we build a
101  // hash table based on poly column which has rte_idx = 1)
102  // but if it's cardinality is smaller than that of polygon table more than 10x
103  // we try to fall back to loop join to avoid too expensive overlaps join cost
104  if (inner_cv->get_rte_idx() == 0 &&
105  (inner_cv->get_type_info().get_type() == kPOINT)) {
106  // outer is poly, and we can use overlaps hash join
107  if (needs_table_reordering && outer_inner_card_ratio > 10.0 &&
108  inner_table_cardinality < 10000) {
109  // but current pt table is small enough and hash table is larger than
110  // the pt table at least 10 times, then we fall back to loop join
111  // to avoid too expensive hash join
112  // so let's try to set inner table as poly table to invalidate
113  // rte index requirement
114  return {200, 200, InnerQualDecision::RHS};
115  } else {
116  // otherwise, try to keep the existing ordering
117  return {180, 190, InnerQualDecision::IGNORE};
118  }
119  } else {
120  // poly is the inner table, so we need to reorder tables to use overlaps hash
121  // join
122  if (needs_table_reordering) {
123  // outer point table is larger than inner poly table, so let's reorder them
124  // by table cardinality
125  return {200, 200, InnerQualDecision::RHS};
126  } else {
127  // otherwise, try to keep the existing ordering
128  return {180, 190, InnerQualDecision::IGNORE};
129  }
130  }
131  }
132  // rest of overlaps-available and overlaps-unavailable geo functions
133  // can reach here, and they are reordered by table cardinality
134  // specifically, overlaps-available geo join functions are satisfied one of
135  // followings: ST_OVERLAPS_sv and is_poly_mpoly_rewrite_target_func we can use
136  // overlaps hash join for those functions regardless of table ordering see
137  // rewrite_overlaps_conjunction function in ExpressionRewrite.cpp
138  VLOG(2) << "Detect geo join operator, initial_inner_table(db_id: "
139  << inner_table_key.db_id << ", table_id: " << inner_table_key.table_id
140  << "), cardinality: " << inner_table_cardinality
141  << "), initial_outer_table(db_id: " << outer_table_key.db_id
142  << ", table_id: " << outer_table_key.table_id
143  << "), cardinality: " << outer_table_cardinality
144  << "), inner_qual_decision: " << inner_qual_decision;
145  return {200, 200, inner_qual_decision};
146  }
147  } else {
148  // let's fall back to the old strategy by ordering tables by types of geometries
149  const auto func_oper = dynamic_cast<const Analyzer::FunctionOper*>(qual);
150  if (func_oper) {
151  std::vector<SQLTypes> geo_types_for_func;
152  for (size_t i = 0; i < func_oper->getArity(); i++) {
153  const auto arg_expr = func_oper->getArg(i);
154  const auto& ti = arg_expr->get_type_info();
155  if (ti.is_geometry() || is_constructed_point(arg_expr)) {
156  geo_types_for_func.push_back(ti.get_type());
157  }
158  }
159  std::regex geo_func_regex("ST_[\\w]*");
160  std::smatch geo_func_match;
161  const auto& func_name = func_oper->getName();
162  if (geo_types_for_func.size() == 2 &&
163  std::regex_match(func_name, geo_func_match, geo_func_regex)) {
164  const auto rhs_cost = GEO_TYPE_COSTS[geo_types_for_func[0]];
165  const auto lhs_cost = GEO_TYPE_COSTS[geo_types_for_func[1]];
166  return {lhs_cost, rhs_cost, InnerQualDecision::IGNORE};
167  }
168  return {200, 200, InnerQualDecision::IGNORE};
169  }
170  }
171  }
172 
173  const auto bin_oper = dynamic_cast<const Analyzer::BinOper*>(qual);
174  if (!bin_oper || !IS_EQUIVALENCE(bin_oper->get_optype())) {
175  return {200, 200, InnerQualDecision::IGNORE};
176  }
177  InnerQualDecision inner_qual_decision = InnerQualDecision::UNKNOWN;
178  if (executor) {
179  try {
180  const auto normalized_bin_oper =
181  HashJoin::normalizeColumnPairs(bin_oper, executor->getTemporaryTables());
182  const auto& inner_outer = normalized_bin_oper.first;
183  // normalization success, so we need to figure out which cv becomes an inner
184  auto lhs = bin_oper->get_left_operand();
185  if (auto lhs_tuple = dynamic_cast<const Analyzer::ExpressionTuple*>(
186  bin_oper->get_left_operand())) {
187  lhs = lhs_tuple->getTuple().front().get();
188  }
189  CHECK(lhs);
190  if (lhs == inner_outer.front().first) {
191  inner_qual_decision = InnerQualDecision::LHS;
192  } else if (lhs == inner_outer.front().second) {
193  inner_qual_decision = InnerQualDecision::RHS;
194  }
195  } catch (const HashJoinFail& e) {
196  return {200, 200, e.inner_qual_decision};
197  } catch (...) {
198  return {200, 200, inner_qual_decision};
199  }
200  }
201  return {100, 100, inner_qual_decision};
202 }
203 
204 // Builds a graph with nesting levels as nodes and join condition costs as edges.
205 std::vector<std::map<node_t, cost_t>> build_join_cost_graph(
206  const JoinQualsPerNestingLevel& left_deep_join_quals,
207  const std::vector<InputTableInfo>& table_infos,
208  const Executor* executor,
209  std::vector<std::map<node_t, InnerQualDecision>>& qual_detection_res) {
210  CHECK_EQ(left_deep_join_quals.size() + 1, table_infos.size());
211  std::vector<std::map<node_t, cost_t>> join_cost_graph(table_infos.size());
213  // Build the constraints graph: nodes are nest levels, edges are the existence of
214  // qualifiers between levels.
215  for (const auto& current_level_join_conditions : left_deep_join_quals) {
216  for (const auto& qual : current_level_join_conditions.quals) {
217  std::set<int> qual_nest_levels = visitor.visit(qual.get());
218  if (qual_nest_levels.size() != 2) {
219  continue;
220  }
221  int lhs_nest_level = *qual_nest_levels.begin();
222  CHECK_GE(lhs_nest_level, 0);
223  qual_nest_levels.erase(qual_nest_levels.begin());
224  int rhs_nest_level = *qual_nest_levels.begin();
225  CHECK_GE(rhs_nest_level, 0);
226 
227  // Get the {lhs, rhs} cost for the qual
228  const auto qual_costing = get_join_qual_cost(qual.get(), executor);
229  qual_detection_res[lhs_nest_level][rhs_nest_level] = std::get<2>(qual_costing);
230  qual_detection_res[rhs_nest_level][lhs_nest_level] = std::get<2>(qual_costing);
231  const auto edge_it = join_cost_graph[lhs_nest_level].find(rhs_nest_level);
232  auto rhs_cost = std::get<1>(qual_costing);
233  if (edge_it == join_cost_graph[lhs_nest_level].end() ||
234  edge_it->second > rhs_cost) {
235  auto lhs_cost = std::get<0>(qual_costing);
236  join_cost_graph[lhs_nest_level][rhs_nest_level] = rhs_cost;
237  join_cost_graph[rhs_nest_level][lhs_nest_level] = lhs_cost;
238  }
239  }
240  }
241  return join_cost_graph;
242 }
243 
244 // Tracks dependencies between nodes.
246  public:
247  SchedulingDependencyTracking(const size_t node_count) : inbound_(node_count) {}
248 
249  // Add a from -> to dependency.
250  void addEdge(const node_t from, const node_t to) { inbound_[to].insert(from); }
251 
252  // Removes from's outbound dependencies.
253  void removeNode(const node_t from) {
254  for (auto& inbound_for_node : inbound_) {
255  inbound_for_node.erase(from);
256  }
257  }
258 
259  // Returns the set of all nodes without dependencies.
260  std::unordered_set<node_t> getRoots() const {
261  std::unordered_set<node_t> roots;
262  for (node_t candidate = 0; candidate < inbound_.size(); ++candidate) {
263  if (inbound_[candidate].empty()) {
264  roots.insert(candidate);
265  }
266  }
267  return roots;
268  }
269 
270  private:
271  std::vector<std::unordered_set<node_t>> inbound_;
272 };
273 
274 // The tree edge for traversal of the cost graph.
278 };
279 
280 // Builds dependency tracking based on left joins
282  const JoinQualsPerNestingLevel& left_deep_join_quals,
283  const std::vector<std::map<node_t, cost_t>>& join_cost_graph) {
284  SchedulingDependencyTracking dependency_tracking(left_deep_join_quals.size() + 1);
285  // Add directed graph edges for left join dependencies.
286  // See also start_it inside traverse_join_cost_graph(). These
287  // edges prevent start_it from pointing to a table with a
288  // left join dependency on another table.
289  for (size_t level_idx = 0; level_idx < left_deep_join_quals.size(); ++level_idx) {
290  if (left_deep_join_quals[level_idx].type == JoinType::LEFT) {
291  dependency_tracking.addEdge(level_idx, level_idx + 1);
292  }
293  }
294  return dependency_tracking;
295 }
296 
297 // Do a breadth-first traversal of the cost graph. This avoids scheduling a nest level
298 // before the ones which constraint it are scheduled and it favors equi joins over loop
299 // joins.
300 std::vector<node_t> traverse_join_cost_graph(
301  const std::vector<std::map<node_t, cost_t>>& join_cost_graph,
302  const std::vector<InputTableInfo>& table_infos,
303  const std::function<bool(const node_t lhs_nest_level, const node_t rhs_nest_level)>&
304  compare_node,
305  const std::function<bool(const TraversalEdge&, const TraversalEdge&)>& compare_edge,
306  const JoinQualsPerNestingLevel& left_deep_join_quals,
307  std::vector<std::map<node_t, InnerQualDecision>>& qual_normalization_res) {
308  std::vector<node_t> all_nest_levels(table_infos.size());
309  std::iota(all_nest_levels.begin(), all_nest_levels.end(), 0);
310  std::vector<node_t> input_permutation;
311  std::unordered_set<node_t> visited;
312  auto dependency_tracking =
313  build_dependency_tracking(left_deep_join_quals, join_cost_graph);
314  auto schedulable_node = [&dependency_tracking, &visited](const node_t node) {
315  const auto nodes_ready = dependency_tracking.getRoots();
316  return nodes_ready.find(node) != nodes_ready.end() &&
317  visited.find(node) == visited.end();
318  };
319  while (visited.size() < table_infos.size()) {
320  // Filter out nest levels which are already visited or have pending dependencies.
321  std::vector<node_t> remaining_nest_levels;
322  std::copy_if(all_nest_levels.begin(),
323  all_nest_levels.end(),
324  std::back_inserter(remaining_nest_levels),
325  schedulable_node);
326  CHECK(!remaining_nest_levels.empty());
327  // Start with the table with most tuples.
328  const auto start_it = std::max_element(
329  remaining_nest_levels.begin(), remaining_nest_levels.end(), compare_node);
330  CHECK(start_it != remaining_nest_levels.end());
331  std::priority_queue<TraversalEdge, std::vector<TraversalEdge>, decltype(compare_edge)>
332  worklist(compare_edge);
333  // look at all edges, compare the
334  // cost of our edge vs theirs, and pick the best start edge
335  node_t start = *start_it;
336  // we adaptively switch the inner and outer when we have a chance to exploit
337  // hash join framework for a query with a single binary join
338  TraversalEdge start_edge{start, 0};
339 
340  // when we have a single binary join in the query, we can analyze the qual and apply
341  // more smart table reordering logic that maximizes the chance of exploiting hash join
342  // todo (yoonmin) : generalize this for an arbitrary join pipeline
343  if (remaining_nest_levels.size() == 2 && qual_normalization_res[start].size() == 1) {
344  auto inner_qual_decision = qual_normalization_res[start].begin()->second;
345  auto join_qual = left_deep_join_quals.begin()->quals;
346  using ColvarSet =
347  std::set<const Analyzer::ColumnVar*,
348  bool (*)(const Analyzer::ColumnVar*, const Analyzer::ColumnVar*)>;
349 
350  auto set_new_rte_idx = [](ColvarSet& cv_set, int new_rte) {
351  std::for_each(
352  cv_set.begin(), cv_set.end(), [new_rte](const Analyzer::ColumnVar* cv) {
353  const_cast<Analyzer::ColumnVar*>(cv)->set_rte_idx(new_rte);
354  });
355  };
356 
357  // IGNORE: use the existing table reordering logic
358  // KEEP: return the existing table permutation and related cvs (column variables)
359  // SWAP: change the starting table of the table reordering logic and relevant
360  // columns' rte index
361  enum class Decision { IGNORE, KEEP, SWAP };
362 
363  auto analyze_join_qual = [&start,
364  &remaining_nest_levels,
365  &inner_qual_decision,
366  &table_infos,
367  compare_node](const std::shared_ptr<Analyzer::Expr>& lhs,
368  ColvarSet& lhs_colvar_set,
369  const std::shared_ptr<Analyzer::Expr>& rhs,
370  ColvarSet& rhs_colvar_set) {
371  if (!lhs || !rhs || lhs_colvar_set.empty() || rhs_colvar_set.empty()) {
372  return std::make_pair(Decision::IGNORE, start);
373  }
374 
375  auto alternative_it = std::find_if(
376  remaining_nest_levels.begin(),
377  remaining_nest_levels.end(),
378  [start](const size_t nest_level) { return start != nest_level; });
379  CHECK(alternative_it != remaining_nest_levels.end());
380  auto alternative_rte = *alternative_it;
381 
382  Decision decision = Decision::IGNORE;
383  // inner col's rte should be larger than outer col
384  int inner_rte = -1;
385  int outer_rte = -1;
386  bool is_outer_col_valid = false;
387  auto check_expr_is_valid_col = [&is_outer_col_valid](const Analyzer::Expr* expr) {
388  if (auto expr_tuple = dynamic_cast<const Analyzer::ExpressionTuple*>(expr)) {
389  for (auto& inner_expr : expr_tuple->getTuple()) {
390  auto cv_from_expr =
391  HashJoin::getHashJoinColumn<Analyzer::ColumnVar>(inner_expr.get());
392  if (!cv_from_expr) {
393  is_outer_col_valid = false;
394  return;
395  }
396  }
397  } else {
398  auto cv_from_expr = HashJoin::getHashJoinColumn<Analyzer::ColumnVar>(expr);
399  if (!cv_from_expr) {
400  is_outer_col_valid = false;
401  return;
402  }
403  }
404  is_outer_col_valid = true;
405  };
406  if (inner_qual_decision == InnerQualDecision::LHS) {
407  inner_rte = (*lhs_colvar_set.begin())->get_rte_idx();
408  outer_rte = (*rhs_colvar_set.begin())->get_rte_idx();
409  check_expr_is_valid_col(rhs.get());
410  } else if (inner_qual_decision == InnerQualDecision::RHS) {
411  inner_rte = (*rhs_colvar_set.begin())->get_rte_idx();
412  outer_rte = (*lhs_colvar_set.begin())->get_rte_idx();
413  check_expr_is_valid_col(lhs.get());
414  }
415  if (inner_rte >= 0 && outer_rte >= 0) {
416  const auto inner_cardinality =
417  table_infos[inner_rte].info.getNumTuplesUpperBound();
418  const auto outer_cardinality =
419  table_infos[outer_rte].info.getNumTuplesUpperBound();
420  if (inner_cardinality > g_trivial_loop_join_threshold) {
421  if (inner_rte == static_cast<int>(start)) {
422  // inner is driving the join loop but also has a valid join column
423  // which is available for building a hash table
424  // but ignore swapping when inner's cardinality is larger than that of
425  // outer's / otherwise swap inner and outer (to use the valid inner)
426  decision = is_outer_col_valid && inner_cardinality > outer_cardinality
427  ? Decision::IGNORE
428  : Decision::SWAP;
429  } else {
430  CHECK_EQ(inner_rte, static_cast<int>(alternative_rte));
431  // now, a valid inner column is outer table
432  if (compare_node(inner_rte, start)) {
433  // but outer table is larger than the current inner
434  // so we can exploit the existing table reordering logic
435  decision = Decision::IGNORE;
436  } else {
437  // and outer table is smaller than the current inner
438  // so we do not need to reorder the table starting from the inner
439  decision = Decision::KEEP;
440  }
441  }
442  }
443  }
444 
445  if (decision == Decision::KEEP) {
446  return std::make_pair(decision, start);
447  } else if (decision == Decision::SWAP) {
448  return std::make_pair(decision, alternative_rte);
449  }
450  return std::make_pair(Decision::IGNORE, start);
451  };
452 
453  auto collect_colvars = [](const std::shared_ptr<Analyzer::Expr> expr,
454  ColvarSet& cv_set) {
455  expr->collect_column_var(cv_set, false);
456  };
457 
458  auto adjust_reordering_logic = [&start, &start_edge, &start_it, set_new_rte_idx](
459  Decision decision,
460  int alternative_rte,
461  ColvarSet& lhs_colvar_set,
462  ColvarSet& rhs_colvar_set) {
463  CHECK(decision == Decision::SWAP);
464  start = alternative_rte;
465  set_new_rte_idx(lhs_colvar_set, alternative_rte);
466  set_new_rte_idx(rhs_colvar_set, *start_it);
467  start_edge.join_cost = 0;
468  start_edge.nest_level = start;
469  };
470 
471  auto bin_op = dynamic_cast<Analyzer::BinOper*>(join_qual.begin()->get());
472  if (bin_op) {
473  auto lhs = bin_op->get_own_left_operand();
474  auto rhs = bin_op->get_own_right_operand();
475  if (auto lhs_exp = dynamic_cast<Analyzer::ExpressionTuple*>(lhs.get())) {
476  // retrieve the decision and info for adjusting reordering by referring the
477  // first cv and apply them to the rest of cvs
478  auto rhs_exp = dynamic_cast<Analyzer::ExpressionTuple*>(rhs.get());
479  CHECK(rhs_exp);
480  auto& lhs_exprs = lhs_exp->getTuple();
481  auto& rhs_exprs = rhs_exp->getTuple();
482  CHECK_EQ(lhs_exprs.size(), rhs_exprs.size());
483  for (size_t i = 0; i < lhs_exprs.size(); ++i) {
484  Decision decision{Decision::IGNORE};
485  int alternative_rte_idx = -1;
486  ColvarSet lhs_colvar_set(Analyzer::ColumnVar::colvar_comp);
487  ColvarSet rhs_colvar_set(Analyzer::ColumnVar::colvar_comp);
488  collect_colvars(lhs_exprs.at(i), lhs_colvar_set);
489  collect_colvars(rhs_exprs.at(i), rhs_colvar_set);
490  if (i == 0) {
491  auto investigation_res =
492  analyze_join_qual(lhs, lhs_colvar_set, rhs, rhs_colvar_set);
493  decision = investigation_res.first;
494  if (decision == Decision::KEEP) {
495  return remaining_nest_levels;
496  }
497  alternative_rte_idx = investigation_res.second;
498  }
499  if (decision == Decision::SWAP) {
500  adjust_reordering_logic(
501  decision, alternative_rte_idx, lhs_colvar_set, rhs_colvar_set);
502  }
503  }
504  } else {
505  ColvarSet lhs_colvar_set(Analyzer::ColumnVar::colvar_comp);
506  ColvarSet rhs_colvar_set(Analyzer::ColumnVar::colvar_comp);
507  collect_colvars(lhs, lhs_colvar_set);
508  collect_colvars(rhs, rhs_colvar_set);
509  auto investigation_res =
510  analyze_join_qual(lhs, lhs_colvar_set, rhs, rhs_colvar_set);
511  if (investigation_res.first == Decision::KEEP) {
512  return remaining_nest_levels;
513  } else if (investigation_res.first == Decision::SWAP) {
514  adjust_reordering_logic(investigation_res.first,
515  investigation_res.second,
516  lhs_colvar_set,
517  rhs_colvar_set);
518  }
519  }
520  }
521  }
522 
523  VLOG(2) << "Table reordering starting with nest level " << start;
524  for (const auto& graph_edge : join_cost_graph[*start_it]) {
525  const node_t succ = graph_edge.first;
526  if (!schedulable_node(succ)) {
527  continue;
528  }
529  const TraversalEdge succ_edge{succ, graph_edge.second};
530  for (const auto& successor_edge : join_cost_graph[succ]) {
531  if (successor_edge.first == start) {
532  start_edge.join_cost = successor_edge.second;
533  // lhs cost / num tuples less than rhs cost if compare edge is true, swap nest
534  // levels
535  if (compare_edge(start_edge, succ_edge)) {
536  VLOG(2) << "Table reordering changing start nest level from " << start
537  << " to " << succ;
538  start = succ;
539  start_edge = succ_edge;
540  }
541  }
542  }
543  }
544  VLOG(2) << "Table reordering picked start nest level " << start << " with cost "
545  << start_edge.join_cost;
546  CHECK_EQ(start, start_edge.nest_level);
547  worklist.push(start_edge);
548  const auto it_ok = visited.insert(start);
549  CHECK(it_ok.second);
550  while (!worklist.empty()) {
551  // Extract a node and add it to the permutation.
552  TraversalEdge crt = worklist.top();
553  worklist.pop();
554  VLOG(1) << "Insert input permutation, idx: " << input_permutation.size()
555  << ", nest_level: " << crt.nest_level;
556  input_permutation.push_back(crt.nest_level);
557  dependency_tracking.removeNode(crt.nest_level);
558  // Add successors which are ready and not visited yet to the queue.
559  for (const auto& graph_edge : join_cost_graph[crt.nest_level]) {
560  const node_t succ = graph_edge.first;
561  if (!schedulable_node(succ)) {
562  continue;
563  }
564  worklist.push(TraversalEdge{succ, graph_edge.second});
565  const auto it_ok = visited.insert(succ);
566  CHECK(it_ok.second);
567  }
568  }
569  }
570  return input_permutation;
571 }
572 
573 } // namespace
574 
575 std::vector<node_t> get_node_input_permutation(
576  const JoinQualsPerNestingLevel& left_deep_join_quals,
577  const std::vector<InputTableInfo>& table_infos,
578  const Executor* executor) {
579  std::vector<std::map<node_t, InnerQualDecision>> qual_normalization_res(
580  table_infos.size());
581  const auto join_cost_graph = build_join_cost_graph(
582  left_deep_join_quals, table_infos, executor, qual_normalization_res);
583  // Use the number of tuples in each table to break ties in BFS.
584  const auto compare_node = [&table_infos](const node_t lhs_nest_level,
585  const node_t rhs_nest_level) {
586  return table_infos[lhs_nest_level].info.getNumTuplesUpperBound() <
587  table_infos[rhs_nest_level].info.getNumTuplesUpperBound();
588  };
589  const auto compare_edge = [&compare_node](const TraversalEdge& lhs_edge,
590  const TraversalEdge& rhs_edge) {
591  // Only use the number of tuples as a tie-breaker, if costs are equal.
592  if (lhs_edge.join_cost == rhs_edge.join_cost) {
593  return compare_node(lhs_edge.nest_level, rhs_edge.nest_level);
594  }
595  return lhs_edge.join_cost > rhs_edge.join_cost;
596  };
597  return traverse_join_cost_graph(join_cost_graph,
598  table_infos,
599  compare_node,
600  compare_edge,
601  left_deep_join_quals,
602  qual_normalization_res);
603 }
#define CHECK_EQ(x, y)
Definition: Logger.h:301
static bool colvar_comp(const ColumnVar *l, const ColumnVar *r)
Definition: Analyzer.h:215
#define IS_EQUIVALENCE(X)
Definition: sqldefs.h:69
SchedulingDependencyTracking build_dependency_tracking(const JoinQualsPerNestingLevel &left_deep_join_quals, const std::vector< std::map< node_t, cost_t >> &join_cost_graph)
bool is_constructed_point(const Analyzer::Expr *expr)
Definition: Execute.h:1508
static std::unordered_map< SQLTypes, cost_t > std::tuple< cost_t, cost_t, InnerQualDecision > get_join_qual_cost(const Analyzer::Expr *qual, const Executor *executor)
const TableDescriptor * get_metadata_for_table(const ::shared::TableKey &table_key, bool populate_fragmenter)
#define CHECK_GE(x, y)
Definition: Logger.h:306
const std::vector< const Analyzer::ColumnVar * > & getGeoArgCvs() const
std::vector< JoinCondition > JoinQualsPerNestingLevel
T visit(const Analyzer::Expr *expr) const
unsigned g_trivial_loop_join_threshold
Definition: Execute.cpp:89
std::vector< node_t > get_node_input_permutation(const JoinQualsPerNestingLevel &left_deep_join_quals, const std::vector< InputTableInfo > &table_infos, const Executor *executor)
static std::unordered_map< SQLTypes, cost_t > GEO_TYPE_COSTS
#define CHECK_NE(x, y)
Definition: Logger.h:302
std::vector< std::map< node_t, cost_t > > build_join_cost_graph(const JoinQualsPerNestingLevel &left_deep_join_quals, const std::vector< InputTableInfo > &table_infos, const Executor *executor, std::vector< std::map< node_t, InnerQualDecision >> &qual_detection_res)
const std::string & getGeoFunctionName() const
static bool is_poly_point_rewrite_target_func(std::string_view target_func_name)
const SQLTypeInfo & get_type_info() const
Definition: Analyzer.h:79
const Analyzer::Expr * getArg(const size_t i) const
Definition: Analyzer.h:2410
DEVICE void iota(ARGS &&...args)
Definition: gpu_enabled.h:69
#define CHECK(condition)
Definition: Logger.h:291
static std::pair< std::vector< InnerOuter >, std::vector< InnerOuterStringOpInfos > > normalizeColumnPairs(const Analyzer::BinOper *condition, const TemporaryTables *temporary_tables)
Definition: HashJoin.cpp:996
InnerQualDecision
Definition: HashJoin.h:63
std::vector< node_t > traverse_join_cost_graph(const std::vector< std::map< node_t, cost_t >> &join_cost_graph, const std::vector< InputTableInfo > &table_infos, const std::function< bool(const node_t lhs_nest_level, const node_t rhs_nest_level)> &compare_node, const std::function< bool(const TraversalEdge &, const TraversalEdge &)> &compare_edge, const JoinQualsPerNestingLevel &left_deep_join_quals, std::vector< std::map< node_t, InnerQualDecision >> &qual_normalization_res)
AccessManager::Decision Decision
InnerQualDecision inner_qual_decision
Definition: HashJoin.h:79
const std::shared_ptr< Analyzer::Expr > get_own_left_operand() const
Definition: Analyzer.h:457
#define VLOG(n)
Definition: Logger.h:387
static bool is_point_poly_rewrite_target_func(std::string_view target_func_name)
const std::pair< shared::TableKey, shared::TableKey > getTableIdsOfGeoExpr() const