std::size_t SimpleCostModel::estimateCardinality( const P::PhysicalPtr &physical_plan) { switch (physical_plan->getPhysicalType()) { case P::PhysicalType::kTopLevelPlan: return estimateCardinalityForTopLevelPlan( std::static_pointer_cast<const P::TopLevelPlan>(physical_plan)); case P::PhysicalType::kTableReference: return estimateCardinalityForTableReference( std::static_pointer_cast<const P::TableReference>(physical_plan)); case P::PhysicalType::kSelection: return estimateCardinalityForSelection( std::static_pointer_cast<const P::Selection>(physical_plan)); case P::PhysicalType::kTableGenerator: return estimateCardinalityForTableGenerator( std::static_pointer_cast<const P::TableGenerator>(physical_plan)); case P::PhysicalType::kHashJoin: return estimateCardinalityForHashJoin( std::static_pointer_cast<const P::HashJoin>(physical_plan)); case P::PhysicalType::kNestedLoopsJoin: return estimateCardinalityForNestedLoopsJoin( std::static_pointer_cast<const P::NestedLoopsJoin>(physical_plan)); case P::PhysicalType::kAggregate: return estimateCardinalityForAggregate( std::static_pointer_cast<const P::Aggregate>(physical_plan)); case P::PhysicalType::kSharedSubplanReference: { const P::SharedSubplanReferencePtr shared_subplan_reference = std::static_pointer_cast<const P::SharedSubplanReference>(physical_plan); return estimateCardinality( shared_subplans_[shared_subplan_reference->subplan_id()]); } default: LOG(FATAL) << "Unsupported physical plan:" << physical_plan->toString(); } }
P::PhysicalPtr ReduceGroupByAttributes::applyInternal(const P::PhysicalPtr &input) { std::vector<P::PhysicalPtr> new_children; for (const P::PhysicalPtr &child : input->children()) { new_children.push_back(applyInternal(child)); } if (new_children != input->children()) { return applyToNode(input->copyWithNewChildren(new_children)); } else { return applyToNode(input); } }
P::PhysicalPtr InjectJoinFilters::transformHashJoinToFilters( const P::PhysicalPtr &input) const { std::vector<P::PhysicalPtr> new_children; for (const P::PhysicalPtr &child : input->children()) { new_children.emplace_back(transformHashJoinToFilters(child)); } P::HashJoinPtr hash_join; if (P::SomeHashJoin::MatchesWithConditionalCast(input, &hash_join) && isTransformable(hash_join)) { const bool is_anti_join = hash_join->join_type() == P::HashJoin::JoinType::kLeftAntiJoin; DCHECK_EQ(2u, new_children.size()); P::PhysicalPtr build_child = new_children[1]; E::PredicatePtr build_side_filter_predicate = nullptr; P::SelectionPtr selection; if (hash_join->build_predicate() == nullptr) { if (P::SomeSelection::MatchesWithConditionalCast(build_child, &selection) && E::SubsetOfExpressions(hash_join->right_join_attributes(), selection->input()->getOutputAttributes())) { build_child = selection->input(); build_side_filter_predicate = selection->filter_predicate(); } } else { build_child = hash_join->right(); build_side_filter_predicate = hash_join->build_predicate(); } return P::FilterJoin::Create(new_children[0], build_child, hash_join->left_join_attributes(), hash_join->right_join_attributes(), hash_join->project_expressions(), build_side_filter_predicate, is_anti_join, hash_join->hasRepartition(), hash_join->cloneOutputPartitionSchemeHeader()); } if (input->children() != new_children) { return input->copyWithNewChildren(new_children); } else { return input; } }
physical::PhysicalPtr InjectJoinFilters::pushDownFiltersInternal( const physical::PhysicalPtr &probe_child, const physical::PhysicalPtr &build_child, const physical::FilterJoinPtr &filter_join) const { switch (probe_child->getPhysicalType()) { case P::PhysicalType::kAggregate: // Fall through case P::PhysicalType::kHashJoin: case P::PhysicalType::kSample: case P::PhysicalType::kSelection: case P::PhysicalType::kSort: case P::PhysicalType::kWindowAggregate: { DCHECK_GE(probe_child->getNumChildren(), 1u); const P::PhysicalPtr child = probe_child->children()[0]; if (E::SubsetOfExpressions(filter_join->probe_attributes(), child->getOutputAttributes())) { const P::PhysicalPtr new_child = pushDownFiltersInternal(child, build_child, filter_join); if (new_child != child) { std::vector<P::PhysicalPtr> new_children = probe_child->children(); new_children[0] = new_child; return probe_child->copyWithNewChildren(new_children); } } } default: break; } if (probe_child != filter_join->left()) { // TODO(jianqiao): may need to update probe_attributes. return P::FilterJoin::Create(probe_child, build_child, filter_join->probe_attributes(), filter_join->build_attributes(), E::ToNamedExpressions(probe_child->getOutputAttributes()), filter_join->build_side_filter_predicate(), filter_join->is_anti_join(), filter_join->hasRepartition(), filter_join->cloneOutputPartitionSchemeHeader()); } else { return filter_join; } }
P::PhysicalPtr ReduceGroupByAttributes::apply(const P::PhysicalPtr &input) { DCHECK(input->getPhysicalType() == P::PhysicalType::kTopLevelPlan); cost_model_.reset(new cost::StarSchemaSimpleCostModel( std::static_pointer_cast<const P::TopLevelPlan>(input)->shared_subplans())); P::PhysicalPtr output = applyInternal(input); if (output != input) { output = PruneColumns().apply(output); } return output; }
P::PhysicalPtr InjectJoinFilters::apply(const P::PhysicalPtr &input) { DCHECK(input->getPhysicalType() == P::PhysicalType::kTopLevelPlan); const P::TopLevelPlanPtr top_level_plan = std::static_pointer_cast<const P::TopLevelPlan>(input); cost_model_.reset( new cost::StarSchemaSimpleCostModel( top_level_plan->shared_subplans())); lip_filter_configuration_.reset(new P::LIPFilterConfiguration()); // Step 1. Transform applicable HashJoin nodes to FilterJoin nodes. P::PhysicalPtr output = transformHashJoinToFilters(input); if (output == input) { return input; } // Step 2. If the top level plan is a filter join, wrap it with a Selection // to stabilize output columns. output = wrapSelection(output); // Step 3. Push down FilterJoin nodes to be evaluated early. output = pushDownFilters(output); // Step 4. Add Selection nodes for attaching the LIPFilters, if necessary. output = addFilterAnchors(output, false); // Step 5. Because of the pushdown of FilterJoin nodes, there are optimization // opportunities for projecting columns early. output = PruneColumns().apply(output); // Step 6. For each FilterJoin node, attach its corresponding LIPFilter to // proper nodes. concretizeAsLIPFilters(output, nullptr); if (!lip_filter_configuration_->getBuildInfoMap().empty() || !lip_filter_configuration_->getProbeInfoMap().empty()) { output = std::static_pointer_cast<const P::TopLevelPlan>(output) ->copyWithLIPFilterConfiguration( P::LIPFilterConfigurationPtr(lip_filter_configuration_.release())); } return output; }
void InjectJoinFilters::concretizeAsLIPFilters( const P::PhysicalPtr &input, const P::PhysicalPtr &anchor_node) const { switch (input->getPhysicalType()) { case P::PhysicalType::kAggregate: { const P::AggregatePtr &aggregate = std::static_pointer_cast<const P::Aggregate>(input); concretizeAsLIPFilters(aggregate->input(), aggregate); break; } case P::PhysicalType::kSelection: { const P::SelectionPtr &selection = std::static_pointer_cast<const P::Selection>(input); concretizeAsLIPFilters(selection->input(), selection); break; } // Currently we disable the attachment of filters to HashJoin nodes. See the // comments in InjectJoinFilters::addFilterAnchors(). /* case P::PhysicalType::kHashJoin: { const P::HashJoinPtr &hash_join = std::static_pointer_cast<const P::HashJoin>(input); concretizeAsLIPFilters(hash_join->left(), hash_join); concretizeAsLIPFilters(hash_join->right(), nullptr); break; } */ case P::PhysicalType::kFilterJoin: { const P::FilterJoinPtr &filter_join = std::static_pointer_cast<const P::FilterJoin>(input); DCHECK_EQ(1u, filter_join->build_attributes().size()); const E::AttributeReferencePtr &build_attr = filter_join->build_attributes().front(); std::int64_t min_cpp_value; std::int64_t max_cpp_value; const bool has_exact_min_max_stats = findExactMinMaxValuesForAttributeHelper(filter_join, build_attr, &min_cpp_value, &max_cpp_value); DCHECK(has_exact_min_max_stats); DCHECK_GE(max_cpp_value, min_cpp_value); DCHECK_LE(max_cpp_value - min_cpp_value, kMaxFilterSize); CHECK(anchor_node != nullptr); lip_filter_configuration_->addBuildInfo( P::BitVectorExactFilterBuildInfo::Create(build_attr, min_cpp_value, max_cpp_value, filter_join->is_anti_join()), filter_join); lip_filter_configuration_->addProbeInfo( P::LIPFilterProbeInfo::Create(filter_join->probe_attributes().front(), build_attr, filter_join), anchor_node); concretizeAsLIPFilters(filter_join->left(), anchor_node); concretizeAsLIPFilters(filter_join->right(), filter_join); break; } default: { for (const P::PhysicalPtr &child : input->children()) { concretizeAsLIPFilters(child, nullptr); } } } }
P::PhysicalPtr ReduceGroupByAttributes::applyToNode(const P::PhysicalPtr &input) { P::TableReferencePtr table_reference; if (P::SomeTableReference::MatchesWithConditionalCast(input, &table_reference)) { // Collect the attributes-to-TableReference mapping info. for (const auto &attr : table_reference->attribute_list()) { source_.emplace(attr->id(), std::make_pair(table_reference, attr)); } return input; } P::AggregatePtr aggregate; if (!P::SomeAggregate::MatchesWithConditionalCast(input, &aggregate) || aggregate->grouping_expressions().size() <= 1u) { return input; } // Divide the group-by attributes into groups based on their source table. std::map<P::TableReferencePtr, std::vector<E::AttributeReferencePtr>> table_attributes; for (const auto &expr : aggregate->grouping_expressions()) { const auto source_it = source_.find(expr->id()); if (source_it != source_.end()) { table_attributes[source_it->second.first].emplace_back(source_it->second.second); } } std::unordered_set<E::ExprId> erased_grouping_attr_ids; std::vector<std::pair<P::TableReferencePtr, E::AttributeReferencePtr>> hoisted_tables; // For each group (i.e. each source table), if it is profitable then we pull // the table up the aggregation. for (const auto &pair : table_attributes) { const P::TableReferencePtr table = pair.first; const std::vector<E::AttributeReferencePtr> &attributes = pair.second; // TODO(jianqiao): find a cost-based metic instead of hard-coding the threshold // number of group-by attributes. if (attributes.size() <= FLAGS_reduce_group_by_attributes_threshold) { continue; } std::vector<AttributeInfo> attr_infos; for (const auto &attr : attributes) { attr_infos.emplace_back(attr, cost_model_->impliesUniqueAttributes(table, {attr}), !attr->getValueType().isVariableLength(), attr->getValueType().maximumByteLength()); } std::vector<const AttributeInfo *> attr_info_refs; for (const auto &info : attr_infos) { attr_info_refs.emplace_back(&info); } std::sort(attr_info_refs.begin(), attr_info_refs.end(), AttributeInfo::IsBetterThan); const AttributeInfo &best_candidate = *attr_info_refs.front(); if (!best_candidate.is_unique) { // Cannot find a key attribute. Give up pulling this table up. continue; } const E::AttributeReferencePtr key_attribute = best_candidate.attribute; hoisted_tables.emplace_back(table, key_attribute); for (const auto &attr : attributes) { if (attr->id() != key_attribute->id()) { erased_grouping_attr_ids.emplace(attr->id()); } } } if (erased_grouping_attr_ids.empty()) { return input; } // Reconstuct the Aggregate node with reduced group-by attributes and then // construct HashJoin nodes on top of the Aggregate. std::vector<E::NamedExpressionPtr> reduced_grouping_expressions; for (const auto &expr : aggregate->grouping_expressions()) { if (erased_grouping_attr_ids.find(expr->id()) == erased_grouping_attr_ids.end()) { reduced_grouping_expressions.emplace_back(expr); } } const P::AggregatePtr new_aggregate = P::Aggregate::Create(aggregate->input(), reduced_grouping_expressions, aggregate->aggregate_expressions(), aggregate->filter_predicate()); P::PhysicalPtr output = new_aggregate; std::vector<E::NamedExpressionPtr> project_expressions = E::ToNamedExpressions(output->getOutputAttributes()); for (const auto &pair : hoisted_tables) { const P::TableReferencePtr &source_table = pair.first; const E::AttributeReferencePtr &probe_attribute = pair.second; E::AttributeReferencePtr build_attribute; std::vector<E::AttributeReferencePtr> new_attribute_list; for (const auto &attr : source_table->attribute_list()) { if (attr->id() == probe_attribute->id()) { build_attribute = E::AttributeReference::Create(optimizer_context_->nextExprId(), attr->attribute_name(), attr->attribute_alias(), attr->relation_name(), attr->getValueType(), E::AttributeReferenceScope::kLocal); new_attribute_list.emplace_back(build_attribute); } else { new_attribute_list.emplace_back(attr); project_expressions.emplace_back(attr); } } DCHECK(build_attribute != nullptr); const P::TableReferencePtr build_side_table = P::TableReference::Create(source_table->relation(), source_table->relation()->getName(), new_attribute_list); output = P::HashJoin::Create(output, build_side_table, {probe_attribute}, {build_attribute}, nullptr, project_expressions, P::HashJoin::JoinType::kInnerJoin); } return output; }
bool OneToOne::generatePlan(const L::LogicalPtr &logical_input, P::PhysicalPtr *physical_output) { switch (logical_input->getLogicalType()) { case L::LogicalType::kTopLevelPlan: { const L::TopLevelPlanPtr top_level_plan = std::static_pointer_cast<const L::TopLevelPlan>(logical_input); const P::PhysicalPtr main_physical_plan = physical_mapper_->createOrGetPhysicalFromLogical(top_level_plan->plan()); std::vector<P::PhysicalPtr> shared_physical_subplans; for (const L::LogicalPtr &shared_logical_subplan : top_level_plan->shared_subplans()) { shared_physical_subplans.emplace_back( physical_mapper_->createOrGetPhysicalFromLogical(shared_logical_subplan)); } *physical_output = P::TopLevelPlan::Create(main_physical_plan, shared_physical_subplans); return true; } case L::LogicalType::kSharedSubplanReference: { const L::SharedSubplanReferencePtr shared_subplan_reference = std::static_pointer_cast<const L::SharedSubplanReference>(logical_input); *physical_output = P::SharedSubplanReference::Create(shared_subplan_reference->subplan_id(), shared_subplan_reference->referenced_attributes(), shared_subplan_reference->output_attributes()); return true; } case L::LogicalType::kTableReference: { const L::TableReferencePtr table_reference = std::static_pointer_cast<const L::TableReference>(logical_input); *physical_output = P::TableReference::Create(table_reference->catalog_relation(), table_reference->relation_alias(), table_reference->attribute_list()); return true; } case L::LogicalType::kCopyFrom: { const L::CopyFromPtr copy_from = std::static_pointer_cast<const L::CopyFrom>(logical_input); *physical_output = P::CopyFrom::Create( copy_from->catalog_relation(), copy_from->file_name(), copy_from->column_delimiter(), copy_from->escape_strings()); return true; } case L::LogicalType::kCreateIndex: { const L::CreateIndexPtr create_index = std::static_pointer_cast<const L::CreateIndex>(logical_input); *physical_output = P::CreateIndex::Create(physical_mapper_->createOrGetPhysicalFromLogical( create_index->input()), create_index->index_name(), create_index->index_attributes(), create_index->index_description()); return true; } case L::LogicalType::kCreateTable: { const L::CreateTablePtr create_table = std::static_pointer_cast<const L::CreateTable>(logical_input); *physical_output = P::CreateTable::Create(create_table->relation_name(), create_table->attributes(), create_table->block_properties()); return true; } case L::LogicalType::kDeleteTuples: { const L::DeleteTuplesPtr delete_tuples = std::static_pointer_cast<const L::DeleteTuples>(logical_input); *physical_output = P::DeleteTuples::Create( physical_mapper_->createOrGetPhysicalFromLogical(delete_tuples->input()), delete_tuples->predicate()); return true; } case L::LogicalType::kDropTable: { const L::DropTablePtr drop_table = std::static_pointer_cast<const L::DropTable>(logical_input); *physical_output = P::DropTable::Create(drop_table->catalog_relation()); return true; } case L::LogicalType::kInsertSelection: { const L::InsertSelectionPtr insert_selection = std::static_pointer_cast<const L::InsertSelection>(logical_input); *physical_output = P::InsertSelection::Create( physical_mapper_->createOrGetPhysicalFromLogical(insert_selection->destination()), physical_mapper_->createOrGetPhysicalFromLogical(insert_selection->selection())); return true; } case L::LogicalType::kInsertTuple: { const L::InsertTuplePtr insert_tuple = std::static_pointer_cast<const L::InsertTuple>(logical_input); *physical_output = P::InsertTuple::Create( physical_mapper_->createOrGetPhysicalFromLogical(insert_tuple->input()), insert_tuple->column_values()); return true; } case L::LogicalType::kSample: { const L::SamplePtr sample = std::static_pointer_cast<const L::Sample>(logical_input); *physical_output = P::Sample::Create( physical_mapper_->createOrGetPhysicalFromLogical(sample->input()), sample->is_block_sample(), sample->percentage()); return true; } case L::LogicalType::kSort: { const L::Sort *sort = static_cast<const L::Sort*>(logical_input.get()); const P::PhysicalPtr physical_input = physical_mapper_->createOrGetPhysicalFromLogical(sort->input()); // Find non-sort attributes. const std::vector<E::AttributeReferencePtr> input_attributes = physical_input->getOutputAttributes(); E::UnorderedNamedExpressionSet sort_attributes_set(sort->sort_attributes().begin(), sort->sort_attributes().end()); std::vector<E::AttributeReferencePtr> non_sort_attributes; for (const E::AttributeReferencePtr &input_attribute : input_attributes) { if (sort_attributes_set.find(input_attribute) == sort_attributes_set.end()) { non_sort_attributes.emplace_back(input_attribute); } } *physical_output = P::Sort::Create( physical_mapper_->createOrGetPhysicalFromLogical(sort->input()), sort->sort_attributes(), non_sort_attributes, sort->sort_ascending(), sort->nulls_first_flags(), sort->limit()); return true; } case L::LogicalType::kTableGenerator: { const L::TableGeneratorPtr table_generator = std::static_pointer_cast<const L::TableGenerator>(logical_input); *physical_output = P::TableGenerator::Create( table_generator->generator_function_handle(), table_generator->table_alias(), table_generator->attribute_list()); return true; } case L::LogicalType::kUpdateTable: { const L::UpdateTablePtr update_table = std::static_pointer_cast<const L::UpdateTable>(logical_input); *physical_output = P::UpdateTable::Create( physical_mapper_->createOrGetPhysicalFromLogical(update_table->input()), update_table->assignees(), update_table->assignment_expressions(), update_table->predicate()); return true; } default: return false; } }
std::string PlanVisualizer::visualize(const P::PhysicalPtr &input) { DCHECK(input->getPhysicalType() == P::PhysicalType::kTopLevelPlan); const P::TopLevelPlanPtr top_level_plan = std::static_pointer_cast<const P::TopLevelPlan>(input); cost_model_.reset( new C::StarSchemaSimpleCostModel( top_level_plan->shared_subplans())); lip_filter_conf_ = top_level_plan->lip_filter_configuration(); color_map_["TableReference"] = "skyblue"; color_map_["Selection"] = "#90EE90"; color_map_["FilterJoin"] = "pink"; color_map_["FilterJoin(Anti)"] = "pink"; color_map_["HashJoin"] = "red"; color_map_["HashLeftOuterJoin"] = "orange"; color_map_["HashLeftSemiJoin"] = "orange"; color_map_["HashLeftAntiJoin"] = "orange"; visit(input); // Format output graph std::ostringstream graph_oss; graph_oss << "digraph g {\n"; graph_oss << " rankdir=BT\n"; graph_oss << " node [penwidth=2]\n"; graph_oss << " edge [fontsize=16 fontcolor=gray penwidth=2]\n\n"; // Format nodes for (const NodeInfo &node_info : nodes_) { graph_oss << " " << node_info.id << " ["; if (!node_info.labels.empty()) { graph_oss << "label=\"" << EscapeSpecialChars(JoinToString(node_info.labels, " ")) << "\""; } if (!node_info.color.empty()) { graph_oss << " style=filled fillcolor=\"" << node_info.color << "\""; } graph_oss << "]\n"; } graph_oss << "\n"; // Format edges for (const EdgeInfo &edge_info : edges_) { graph_oss << " " << edge_info.src_node_id << " -> " << edge_info.dst_node_id << " ["; if (edge_info.dashed) { graph_oss << "style=dashed "; } if (!edge_info.labels.empty()) { graph_oss << "label=\"" << EscapeSpecialChars(JoinToString(edge_info.labels, " ")) << "\""; } graph_oss << "]\n"; } graph_oss << "}\n"; return graph_oss.str(); }
void PlanVisualizer::visit(const P::PhysicalPtr &input) { int node_id = ++id_counter_; node_id_map_.emplace(input, node_id); std::set<E::ExprId> referenced_ids; for (const auto &attr : input->getReferencedAttributes()) { referenced_ids.emplace(attr->id()); } for (const auto &child : input->children()) { visit(child); int child_id = node_id_map_[child]; edges_.emplace_back(EdgeInfo()); EdgeInfo &edge_info = edges_.back(); edge_info.src_node_id = child_id; edge_info.dst_node_id = node_id; edge_info.dashed = false; if ((input->getPhysicalType() == P::PhysicalType::kHashJoin || input->getPhysicalType() == P::PhysicalType::kFilterJoin) && child == input->children()[1]) { edge_info.dashed = true; } for (const auto &attr : child->getOutputAttributes()) { if (referenced_ids.find(attr->id()) != referenced_ids.end()) { edge_info.labels.emplace_back(attr->attribute_alias()); } } } nodes_.emplace_back(NodeInfo()); NodeInfo &node_info = nodes_.back(); node_info.id = node_id; if (color_map_.find(input->getName()) != color_map_.end()) { node_info.color = color_map_[input->getName()]; } switch (input->getPhysicalType()) { case P::PhysicalType::kTableReference: { const P::TableReferencePtr table_reference = std::static_pointer_cast<const P::TableReference>(input); node_info.labels.emplace_back(table_reference->relation()->getName()); break; } case P::PhysicalType::kHashJoin: { const P::HashJoinPtr hash_join = std::static_pointer_cast<const P::HashJoin>(input); node_info.labels.emplace_back(input->getName()); const auto &left_attributes = hash_join->left_join_attributes(); const auto &right_attributes = hash_join->right_join_attributes(); for (std::size_t i = 0; i < left_attributes.size(); ++i) { node_info.labels.emplace_back( left_attributes[i]->attribute_alias() + " = " + right_attributes[i]->attribute_alias()); } break; } case P::PhysicalType::kFilterJoin: { const P::FilterJoinPtr filter_join = std::static_pointer_cast<const P::FilterJoin>(input); node_info.labels.emplace_back(input->getName()); const auto &probe_attributes = filter_join->probe_attributes(); const auto &build_attributes = filter_join->build_attributes(); for (std::size_t i = 0; i < probe_attributes.size(); ++i) { node_info.labels.emplace_back( probe_attributes[i]->attribute_alias() + " = " + build_attributes[i]->attribute_alias()); } break; } default: { node_info.labels.emplace_back(input->getName()); break; } } const P::PartitionSchemeHeader *partition_scheme_header = input->getOutputPartitionSchemeHeader(); if (partition_scheme_header) { node_info.labels.emplace_back(partition_scheme_header->toString()); } if (lip_filter_conf_ != nullptr) { const auto &build_filters = lip_filter_conf_->getBuildInfoMap(); const auto build_it = build_filters.find(input); if (build_it != build_filters.end()) { for (const auto &build_info : build_it->second) { node_info.labels.emplace_back( std::string("[LIP build] ") + build_info->build_attribute()->attribute_alias()); } } const auto &probe_filters = lip_filter_conf_->getProbeInfoMap(); const auto probe_it = probe_filters.find(input); if (probe_it != probe_filters.end()) { for (const auto &probe_info : probe_it->second) { node_info.labels.emplace_back( std::string("[LIP probe] ") + probe_info->probe_attribute()->attribute_alias()); } } } try { const std::size_t estimated_cardinality = cost_model_->estimateCardinality(input); const double estimated_selectivity = cost_model_->estimateSelectivity(input); node_info.labels.emplace_back( "est. # = " + std::to_string(estimated_cardinality)); node_info.labels.emplace_back( "est. Selectivity = " + std::to_string(estimated_selectivity)); } catch (const std::exception &e) { // NOTE(jianqiao): CostModel::estimateCardinality() may throw UnsupportedPhysicalPlan // exception for some type of physical nodes such as CreateTable. // In this case, we omit the node's cardinality/selectivity information. } }