OmniSciDB  085a039ca4
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Friends Macros Groups Pages
anonymous_namespace{GroupByAndAggregate.cpp} Namespace Reference

Functions

int32_t get_agg_count (const std::vector< Analyzer::Expr * > &target_exprs)
 
bool expr_is_rowid (const Analyzer::Expr *expr, const Catalog_Namespace::Catalog &cat)
 
bool has_count_distinct (const RelAlgExecutionUnit &ra_exe_unit)
 
bool is_column_range_too_big_for_perfect_hash (const ColRangeInfo &col_range_info, const int64_t max_entry_count)
 
bool cardinality_estimate_less_than_column_range (const int64_t cardinality_estimate, const ColRangeInfo &col_range_info)
 
ColRangeInfo get_expr_range_info (const RelAlgExecutionUnit &ra_exe_unit, const std::vector< InputTableInfo > &query_infos, const Analyzer::Expr *expr, Executor *executor)
 
int64_t get_bucketed_cardinality_without_nulls (const ColRangeInfo &col_range_info)
 
KeylessInfo get_keyless_info (const RelAlgExecutionUnit &ra_exe_unit, const std::vector< InputTableInfo > &query_infos, const bool is_group_by, Executor *executor)
 
CountDistinctDescriptors init_count_distinct_descriptors (const RelAlgExecutionUnit &ra_exe_unit, const std::vector< InputTableInfo > &query_infos, const ExecutorDeviceType device_type, Executor *executor)
 

Function Documentation

bool anonymous_namespace{GroupByAndAggregate.cpp}::cardinality_estimate_less_than_column_range ( const int64_t  cardinality_estimate,
const ColRangeInfo col_range_info 
)

Definition at line 120 of file GroupByAndAggregate.cpp.

References ColRangeInfo::max, and ColRangeInfo::min.

Referenced by GroupByAndAggregate::getColRangeInfo().

121  {
122  try {
123  // the cardinality estimate is the size of the baseline hash table. further penalize
124  // the baseline hash table by a factor of 2x due to overhead in computing baseline
125  // hash. This has the overall effect of penalizing baseline hash over perfect hash by
126  // 4x; i.e. if the cardinality of the filtered data is less than 25% of the entry
127  // count of the column, we use baseline hash on the filtered set
128  return checked_int64_t(cardinality_estimate) * 2 <
129  static_cast<int64_t>(checked_int64_t(col_range_info.max) -
130  checked_int64_t(col_range_info.min));
131  } catch (...) {
132  return false;
133  }
134 }
boost::multiprecision::number< boost::multiprecision::cpp_int_backend< 64, 64, boost::multiprecision::signed_magnitude, boost::multiprecision::checked, void >> checked_int64_t

+ Here is the caller graph for this function:

bool anonymous_namespace{GroupByAndAggregate.cpp}::expr_is_rowid ( const Analyzer::Expr expr,
const Catalog_Namespace::Catalog cat 
)

Definition at line 86 of file GroupByAndAggregate.cpp.

References cat(), CHECK_EQ, and get_column_descriptor_maybe().

Referenced by GroupByAndAggregate::getColRangeInfo().

86  {
87  const auto col = dynamic_cast<const Analyzer::ColumnVar*>(expr);
88  if (!col) {
89  return false;
90  }
91  const auto cd =
92  get_column_descriptor_maybe(col->get_column_id(), col->get_table_id(), cat);
93  if (!cd || !cd->isVirtualCol) {
94  return false;
95  }
96  CHECK_EQ("rowid", cd->columnName);
97  return true;
98 }
#define CHECK_EQ(x, y)
Definition: Logger.h:231
std::string cat(Ts &&...args)
const ColumnDescriptor * get_column_descriptor_maybe(const int col_id, const int table_id, const Catalog_Namespace::Catalog &cat)
Definition: Execute.h:220

+ Here is the call graph for this function:

+ Here is the caller graph for this function:

int32_t anonymous_namespace{GroupByAndAggregate.cpp}::get_agg_count ( const std::vector< Analyzer::Expr * > &  target_exprs)

Definition at line 61 of file GroupByAndAggregate.cpp.

References agg_count(), CHECK, Analyzer::Expr::get_type_info(), kAVG, and kSAMPLE.

Referenced by GroupByAndAggregate::codegen().

61  {
62  int32_t agg_count{0};
63  for (auto target_expr : target_exprs) {
64  CHECK(target_expr);
65  const auto agg_expr = dynamic_cast<Analyzer::AggExpr*>(target_expr);
66  if (!agg_expr || agg_expr->get_aggtype() == kSAMPLE) {
67  const auto& ti = target_expr->get_type_info();
68  if (ti.is_buffer()) {
69  agg_count += 2;
70  } else if (ti.is_geometry()) {
71  agg_count += ti.get_physical_coord_cols() * 2;
72  } else {
73  ++agg_count;
74  }
75  continue;
76  }
77  if (agg_expr && agg_expr->get_aggtype() == kAVG) {
78  agg_count += 2;
79  } else {
80  ++agg_count;
81  }
82  }
83  return agg_count;
84 }
const SQLTypeInfo & get_type_info() const
Definition: Analyzer.h:81
#define CHECK(condition)
Definition: Logger.h:223
RUNTIME_EXPORT ALWAYS_INLINE uint64_t agg_count(uint64_t *agg, const int64_t)
Definition: sqldefs.h:74

+ Here is the call graph for this function:

+ Here is the caller graph for this function:

int64_t anonymous_namespace{GroupByAndAggregate.cpp}::get_bucketed_cardinality_without_nulls ( const ColRangeInfo col_range_info)

Definition at line 312 of file GroupByAndAggregate.cpp.

References ColRangeInfo::bucket, CHECK_LT, ColRangeInfo::max, and ColRangeInfo::min.

Referenced by init_count_distinct_descriptors().

312  {
313  if (col_range_info.min <= col_range_info.max) {
314  size_t size = col_range_info.max - col_range_info.min;
315  if (col_range_info.bucket) {
316  size /= col_range_info.bucket;
317  }
318  CHECK_LT(size, std::numeric_limits<int64_t>::max());
319  return static_cast<int64_t>(size + 1);
320  } else {
321  return 0;
322  }
323 }
#define CHECK_LT(x, y)
Definition: Logger.h:233

+ Here is the caller graph for this function:

ColRangeInfo anonymous_namespace{GroupByAndAggregate.cpp}::get_expr_range_info ( const RelAlgExecutionUnit ra_exe_unit,
const std::vector< InputTableInfo > &  query_infos,
const Analyzer::Expr expr,
Executor executor 
)

Definition at line 136 of file GroupByAndAggregate.cpp.

References CHECK, Double, Float, getExpressionRange(), GroupByBaselineHash, GroupByPerfectHash, Integer, Invalid, NonGroupedAggregate, Projection, and RelAlgExecutionUnit::simple_quals.

Referenced by GroupByAndAggregate::codegenGroupBy(), GroupByAndAggregate::codegenPerfectHashFunction(), GroupByAndAggregate::getColRangeInfo(), GroupByAndAggregate::gpuCanHandleOrderEntries(), and init_count_distinct_descriptors().

139  {
140  if (!expr) {
141  return {QueryDescriptionType::Projection, 0, 0, 0, false};
142  }
143 
144  const auto expr_range = getExpressionRange(
145  expr, query_infos, executor, boost::make_optional(ra_exe_unit.simple_quals));
146  switch (expr_range.getType()) {
148  if (expr_range.getIntMin() > expr_range.getIntMax()) {
149  return {
150  QueryDescriptionType::GroupByBaselineHash, 0, -1, 0, expr_range.hasNulls()};
151  }
153  expr_range.getIntMin(),
154  expr_range.getIntMax(),
155  expr_range.getBucket(),
156  expr_range.hasNulls()};
157  }
160  if (expr_range.getFpMin() > expr_range.getFpMax()) {
161  return {
162  QueryDescriptionType::GroupByBaselineHash, 0, -1, 0, expr_range.hasNulls()};
163  }
164  return {QueryDescriptionType::GroupByBaselineHash, 0, 0, 0, false};
165  }
167  return {QueryDescriptionType::GroupByBaselineHash, 0, 0, 0, false};
168  default:
169  CHECK(false);
170  }
171  CHECK(false);
172  return {QueryDescriptionType::NonGroupedAggregate, 0, 0, 0, false};
173 }
ExpressionRange getExpressionRange(const Analyzer::BinOper *expr, const std::vector< InputTableInfo > &query_infos, const Executor *, boost::optional< std::list< std::shared_ptr< Analyzer::Expr >>> simple_quals)
#define CHECK(condition)
Definition: Logger.h:223
std::list< std::shared_ptr< Analyzer::Expr > > simple_quals

+ Here is the call graph for this function:

+ Here is the caller graph for this function:

KeylessInfo anonymous_namespace{GroupByAndAggregate.cpp}::get_keyless_info ( const RelAlgExecutionUnit ra_exe_unit,
const std::vector< InputTableInfo > &  query_infos,
const bool  is_group_by,
Executor executor 
)

This function goes through all target expressions and answers two questions:

  1. Is it possible to have keyless hash?
  2. If yes to 1, then what aggregate expression should be considered to represent the key's presence, if needed (e.g., in detecting empty entries in the result set).

NOTE: Keyless hash is only valid with single-column group by at the moment.

Definition at line 418 of file GroupByAndAggregate.cpp.

References agg_arg(), CHECK, constrained_not_null(), Double, Float, g_bigint_count, get_agg_initial_val(), get_compact_type(), get_target_info(), getExpressionRange(), Integer, Invalid, is_distinct_target(), kAVG, kCOUNT, kMAX, kMIN, kSUM, RelAlgExecutionUnit::quals, takes_float_argument(), and RelAlgExecutionUnit::target_exprs.

Referenced by GroupByAndAggregate::initQueryMemoryDescriptorImpl().

421  {
422  bool keyless{true}, found{false};
423  int32_t num_agg_expr{0};
424  int32_t index{0};
425  for (const auto target_expr : ra_exe_unit.target_exprs) {
426  const auto agg_info = get_target_info(target_expr, g_bigint_count);
427  const auto chosen_type = get_compact_type(agg_info);
428  if (agg_info.is_agg) {
429  num_agg_expr++;
430  }
431  if (!found && agg_info.is_agg && !is_distinct_target(agg_info)) {
432  auto agg_expr = dynamic_cast<const Analyzer::AggExpr*>(target_expr);
433  CHECK(agg_expr);
434  const auto arg_expr = agg_arg(target_expr);
435  const bool float_argument_input = takes_float_argument(agg_info);
436  switch (agg_info.agg_kind) {
437  case kAVG:
438  ++index;
439  if (arg_expr && !arg_expr->get_type_info().get_notnull()) {
440  auto expr_range_info = getExpressionRange(arg_expr, query_infos, executor);
441  if (expr_range_info.getType() == ExpressionRangeType::Invalid ||
442  expr_range_info.hasNulls()) {
443  break;
444  }
445  }
446  found = true;
447  break;
448  case kCOUNT:
449  if (arg_expr && !arg_expr->get_type_info().get_notnull()) {
450  auto expr_range_info = getExpressionRange(arg_expr, query_infos, executor);
451  if (expr_range_info.getType() == ExpressionRangeType::Invalid ||
452  expr_range_info.hasNulls()) {
453  break;
454  }
455  }
456  found = true;
457  break;
458  case kSUM: {
459  auto arg_ti = arg_expr->get_type_info();
460  if (constrained_not_null(arg_expr, ra_exe_unit.quals)) {
461  arg_ti.set_notnull(true);
462  }
463  if (!arg_ti.get_notnull()) {
464  auto expr_range_info = getExpressionRange(arg_expr, query_infos, executor);
465  if (expr_range_info.getType() != ExpressionRangeType::Invalid &&
466  !expr_range_info.hasNulls()) {
467  found = true;
468  }
469  } else {
470  auto expr_range_info = getExpressionRange(arg_expr, query_infos, executor);
471  switch (expr_range_info.getType()) {
474  if (expr_range_info.getFpMax() < 0 || expr_range_info.getFpMin() > 0) {
475  found = true;
476  }
477  break;
479  if (expr_range_info.getIntMax() < 0 || expr_range_info.getIntMin() > 0) {
480  found = true;
481  }
482  break;
483  default:
484  break;
485  }
486  }
487  break;
488  }
489  case kMIN: {
490  CHECK(agg_expr && agg_expr->get_arg());
491  const auto& arg_ti = agg_expr->get_arg()->get_type_info();
492  if (arg_ti.is_string() || arg_ti.is_buffer()) {
493  break;
494  }
495  auto expr_range_info =
496  getExpressionRange(agg_expr->get_arg(), query_infos, executor);
497  auto init_max = get_agg_initial_val(agg_info.agg_kind,
498  chosen_type,
499  is_group_by || float_argument_input,
500  float_argument_input ? sizeof(float) : 8);
501  switch (expr_range_info.getType()) {
504  auto double_max =
505  *reinterpret_cast<const double*>(may_alias_ptr(&init_max));
506  if (expr_range_info.getFpMax() < double_max) {
507  found = true;
508  }
509  break;
510  }
512  if (expr_range_info.getIntMax() < init_max) {
513  found = true;
514  }
515  break;
516  default:
517  break;
518  }
519  break;
520  }
521  case kMAX: {
522  CHECK(agg_expr && agg_expr->get_arg());
523  const auto& arg_ti = agg_expr->get_arg()->get_type_info();
524  if (arg_ti.is_string() || arg_ti.is_buffer()) {
525  break;
526  }
527  auto expr_range_info =
528  getExpressionRange(agg_expr->get_arg(), query_infos, executor);
529  // NULL sentinel and init value for kMAX are identical, which results in
530  // ambiguity in detecting empty keys in presence of nulls.
531  if (expr_range_info.getType() == ExpressionRangeType::Invalid ||
532  expr_range_info.hasNulls()) {
533  break;
534  }
535  auto init_min = get_agg_initial_val(agg_info.agg_kind,
536  chosen_type,
537  is_group_by || float_argument_input,
538  float_argument_input ? sizeof(float) : 8);
539  switch (expr_range_info.getType()) {
542  auto double_min =
543  *reinterpret_cast<const double*>(may_alias_ptr(&init_min));
544  if (expr_range_info.getFpMin() > double_min) {
545  found = true;
546  }
547  break;
548  }
550  if (expr_range_info.getIntMin() > init_min) {
551  found = true;
552  }
553  break;
554  default:
555  break;
556  }
557  break;
558  }
559  default:
560  keyless = false;
561  break;
562  }
563  }
564  if (!keyless) {
565  break;
566  }
567  if (!found) {
568  ++index;
569  }
570  }
571 
572  // shouldn't use keyless for projection only
573  return {
574  keyless && found,
575  index,
576  };
577 }
const Analyzer::Expr * agg_arg(const Analyzer::Expr *expr)
std::vector< Analyzer::Expr * > target_exprs
bool constrained_not_null(const Analyzer::Expr *expr, const std::list< std::shared_ptr< Analyzer::Expr >> &quals)
TargetInfo get_target_info(const PointerType target_expr, const bool bigint_count)
Definition: TargetInfo.h:92
int64_t get_agg_initial_val(const SQLAgg agg, const SQLTypeInfo &ti, const bool enable_compaction, const unsigned min_byte_width_to_compact)
bool takes_float_argument(const TargetInfo &target_info)
Definition: TargetInfo.h:157
Definition: sqldefs.h:75
const SQLTypeInfo get_compact_type(const TargetInfo &target)
bool g_bigint_count
Definition: sqldefs.h:77
bool is_distinct_target(const TargetInfo &target_info)
Definition: TargetInfo.h:153
ExpressionRange getExpressionRange(const Analyzer::BinOper *expr, const std::vector< InputTableInfo > &query_infos, const Executor *, boost::optional< std::list< std::shared_ptr< Analyzer::Expr >>> simple_quals)
Definition: sqldefs.h:78
std::list< std::shared_ptr< Analyzer::Expr > > quals
#define CHECK(condition)
Definition: Logger.h:223
Definition: sqldefs.h:76
Definition: sqldefs.h:74

+ Here is the call graph for this function:

+ Here is the caller graph for this function:

bool anonymous_namespace{GroupByAndAggregate.cpp}::has_count_distinct ( const RelAlgExecutionUnit ra_exe_unit)

Definition at line 100 of file GroupByAndAggregate.cpp.

References g_bigint_count, get_target_info(), is_distinct_target(), and RelAlgExecutionUnit::target_exprs.

Referenced by GroupByAndAggregate::getColRangeInfo().

100  {
101  for (const auto& target_expr : ra_exe_unit.target_exprs) {
102  const auto agg_info = get_target_info(target_expr, g_bigint_count);
103  if (agg_info.is_agg && is_distinct_target(agg_info)) {
104  return true;
105  }
106  }
107  return false;
108 }
std::vector< Analyzer::Expr * > target_exprs
TargetInfo get_target_info(const PointerType target_expr, const bool bigint_count)
Definition: TargetInfo.h:92
bool g_bigint_count
bool is_distinct_target(const TargetInfo &target_info)
Definition: TargetInfo.h:153

+ Here is the call graph for this function:

+ Here is the caller graph for this function:

CountDistinctDescriptors anonymous_namespace{GroupByAndAggregate.cpp}::init_count_distinct_descriptors ( const RelAlgExecutionUnit ra_exe_unit,
const std::vector< InputTableInfo > &  query_infos,
const ExecutorDeviceType  device_type,
Executor executor 
)

Definition at line 579 of file GroupByAndAggregate.cpp.

References Bitmap, CHECK, CHECK_GE, g_bigint_count, g_bitmap_memory_limit, g_enable_watchdog, g_hll_precision_bits, Analyzer::AggExpr::get_arg(), get_bucketed_cardinality_without_nulls(), get_count_distinct_sub_bitmap_count(), get_expr_range_info(), get_target_info(), Analyzer::Expr::get_type_info(), GroupByPerfectHash, hll_size_for_rate(), Invalid, is_distinct_target(), kAPPROX_COUNT_DISTINCT, kCOUNT, kINT, Projection, RelAlgExecutionUnit::target_exprs, and UnorderedSet.

Referenced by GroupByAndAggregate::initQueryMemoryDescriptorImpl().

583  {
584  CountDistinctDescriptors count_distinct_descriptors;
585  for (const auto target_expr : ra_exe_unit.target_exprs) {
586  auto agg_info = get_target_info(target_expr, g_bigint_count);
587  if (is_distinct_target(agg_info)) {
588  CHECK(agg_info.is_agg);
589  CHECK(agg_info.agg_kind == kCOUNT || agg_info.agg_kind == kAPPROX_COUNT_DISTINCT);
590  const auto agg_expr = static_cast<const Analyzer::AggExpr*>(target_expr);
591  const auto& arg_ti = agg_expr->get_arg()->get_type_info();
592  if (arg_ti.is_bytes()) {
593  throw std::runtime_error(
594  "Strings must be dictionary-encoded for COUNT(DISTINCT).");
595  }
596  if (agg_info.agg_kind == kAPPROX_COUNT_DISTINCT && arg_ti.is_buffer()) {
597  throw std::runtime_error("APPROX_COUNT_DISTINCT on arrays not supported yet");
598  }
599  if (agg_info.agg_kind == kAPPROX_COUNT_DISTINCT && arg_ti.is_geometry()) {
600  throw std::runtime_error(
601  "APPROX_COUNT_DISTINCT on geometry columns not supported");
602  }
603  if (agg_info.is_distinct && arg_ti.is_geometry()) {
604  throw std::runtime_error("COUNT DISTINCT on geometry columns not supported");
605  }
606  ColRangeInfo no_range_info{QueryDescriptionType::Projection, 0, 0, 0, false};
607  auto arg_range_info =
608  arg_ti.is_fp() ? no_range_info
610  ra_exe_unit, query_infos, agg_expr->get_arg(), executor);
612  int64_t bitmap_sz_bits{0};
613  if (agg_info.agg_kind == kAPPROX_COUNT_DISTINCT) {
614  const auto error_rate = agg_expr->get_arg1();
615  if (error_rate) {
616  CHECK(error_rate->get_type_info().get_type() == kINT);
617  CHECK_GE(error_rate->get_constval().intval, 1);
618  bitmap_sz_bits = hll_size_for_rate(error_rate->get_constval().smallintval);
619  } else {
620  bitmap_sz_bits = g_hll_precision_bits;
621  }
622  }
623  if (arg_range_info.isEmpty()) {
624  count_distinct_descriptors.emplace_back(
626  0,
627  64,
628  agg_info.agg_kind == kAPPROX_COUNT_DISTINCT,
629  device_type,
630  1});
631  continue;
632  }
633  if (arg_range_info.hash_type_ == QueryDescriptionType::GroupByPerfectHash &&
634  !(arg_ti.is_buffer() || arg_ti.is_geometry())) { // TODO(alex): allow bitmap
635  // implementation for arrays
636  count_distinct_impl_type = CountDistinctImplType::Bitmap;
637  if (agg_info.agg_kind == kCOUNT) {
638  bitmap_sz_bits = get_bucketed_cardinality_without_nulls(arg_range_info);
639  if (bitmap_sz_bits <= 0 || g_bitmap_memory_limit <= bitmap_sz_bits) {
640  count_distinct_impl_type = CountDistinctImplType::UnorderedSet;
641  }
642  }
643  }
644  if (agg_info.agg_kind == kAPPROX_COUNT_DISTINCT &&
645  count_distinct_impl_type == CountDistinctImplType::UnorderedSet &&
646  !(arg_ti.is_array() || arg_ti.is_geometry())) {
647  count_distinct_impl_type = CountDistinctImplType::Bitmap;
648  }
649 
650  if (g_enable_watchdog && !(arg_range_info.isEmpty()) &&
651  count_distinct_impl_type == CountDistinctImplType::UnorderedSet) {
652  throw WatchdogException("Cannot use a fast path for COUNT distinct");
653  }
654  const auto sub_bitmap_count =
655  get_count_distinct_sub_bitmap_count(bitmap_sz_bits, ra_exe_unit, device_type);
656  count_distinct_descriptors.emplace_back(
657  CountDistinctDescriptor{count_distinct_impl_type,
658  arg_range_info.min,
659  bitmap_sz_bits,
660  agg_info.agg_kind == kAPPROX_COUNT_DISTINCT,
661  device_type,
662  sub_bitmap_count});
663  } else {
664  count_distinct_descriptors.emplace_back(CountDistinctDescriptor{
665  CountDistinctImplType::Invalid, 0, 0, false, device_type, 0});
666  }
667  }
668  return count_distinct_descriptors;
669 }
std::vector< Analyzer::Expr * > target_exprs
ColRangeInfo get_expr_range_info(const RelAlgExecutionUnit &ra_exe_unit, const std::vector< InputTableInfo > &query_infos, const Analyzer::Expr *expr, Executor *executor)
int hll_size_for_rate(const int err_percent)
Definition: HyperLogLog.h:115
TargetInfo get_target_info(const PointerType target_expr, const bool bigint_count)
Definition: TargetInfo.h:92
#define CHECK_GE(x, y)
Definition: Logger.h:236
Expr * get_arg() const
Definition: Analyzer.h:1202
int g_hll_precision_bits
size_t get_count_distinct_sub_bitmap_count(const size_t bitmap_sz_bits, const RelAlgExecutionUnit &ra_exe_unit, const ExecutorDeviceType device_type)
std::vector< CountDistinctDescriptor > CountDistinctDescriptors
Definition: CountDistinct.h:36
bool g_bigint_count
bool g_enable_watchdog
int64_t g_bitmap_memory_limit
bool is_distinct_target(const TargetInfo &target_info)
Definition: TargetInfo.h:153
const SQLTypeInfo & get_type_info() const
Definition: Analyzer.h:81
int64_t get_bucketed_cardinality_without_nulls(const ColRangeInfo &col_range_info)
Definition: sqldefs.h:78
CountDistinctImplType
#define CHECK(condition)
Definition: Logger.h:223
Definition: sqltypes.h:45

+ Here is the call graph for this function:

+ Here is the caller graph for this function:

bool anonymous_namespace{GroupByAndAggregate.cpp}::is_column_range_too_big_for_perfect_hash ( const ColRangeInfo col_range_info,
const int64_t  max_entry_count 
)

Definition at line 110 of file GroupByAndAggregate.cpp.

References ColRangeInfo::max, and ColRangeInfo::min.

Referenced by GroupByAndAggregate::getColRangeInfo().

111  {
112  try {
113  return static_cast<int64_t>(checked_int64_t(col_range_info.max) -
114  checked_int64_t(col_range_info.min)) >= max_entry_count;
115  } catch (...) {
116  return true;
117  }
118 }
boost::multiprecision::number< boost::multiprecision::cpp_int_backend< 64, 64, boost::multiprecision::signed_magnitude, boost::multiprecision::checked, void >> checked_int64_t

+ Here is the caller graph for this function: