std::size_t SimpleCostModel::estimateCardinalityForTableReference(
    const P::TableReferencePtr &physical_plan) {
  return physical_plan->relation()->estimateTupleCardinality();
}
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.
  }
}