| 792 | } |
| 793 | |
| 794 | void QueryPlan::convertToDistributed(const QueryPlanOptimizationSettings & optimization_settings) |
| 795 | { |
| 796 | SharedHeader result_header = root->step->getOutputHeader(); |
| 797 | |
| 798 | QueryPlan::Nodes old_nodes = std::move(nodes); |
| 799 | QueryPlan::Node * old_root = root; |
| 800 | root = nullptr; |
| 801 | auto distributed_plan = QueryPlanOptimizations::makeDistributedPlan(std::move(old_nodes), old_root, optimization_settings); |
| 802 | |
| 803 | for (const auto & stage : distributed_plan.stages) |
| 804 | { |
| 805 | auto it = distributed_plan.stage_depends_on.find(stage.first); |
| 806 | const auto & dependencies = it != distributed_plan.stage_depends_on.end() ? it->second : std::unordered_map<String, String>{}; |
| 807 | LOG_TRACE(getLogger("optimize"), "Distributed stage: '{}' depends on: [{}] plan:\n{}", |
| 808 | stage.first, fmt::join(dependencies, ", "), dumpQueryPlan(stage.second.query_plan_fragment)); |
| 809 | } |
| 810 | |
| 811 | if (distributed_plan.stages.size() == 1) |
| 812 | { |
| 813 | /// For now just replace the plan with the first and only fragment, but preserve |
| 814 | /// table locks and storage holders accumulated during planning. |
| 815 | QueryPlanResourceHolder preserved_resources = std::move(resources); |
| 816 | *this = std::move(distributed_plan.stages.begin()->second.query_plan_fragment); |
| 817 | /// QueryPlanResourceHolder's move-assignment appends rhs into lhs without dropping existing entries. |
| 818 | resources = std::move(preserved_resources); |
| 819 | |
| 820 | QueryPlanOptimizationSettings local_settings = optimization_settings; |
| 821 | local_settings.make_distributed_plan = false; |
| 822 | QueryPlanOptimizations::optimizeTreeSecondPass(local_settings, *root, nodes, *this); |
| 823 | } |
| 824 | else |
| 825 | { |
| 826 | ExchangeDescription final_result_exchange |
| 827 | { |
| 828 | .name = "final_result", |
| 829 | .kind = optimization_settings.distributed_plan_force_exchange_kind == "Persisted" ? ExchangeDescription::Kind::Persisted : ExchangeDescription::Kind::Streaming, |
| 830 | .source_bucket_count = 1, |
| 831 | .destination_bucket_count = 1 |
| 832 | }; |
| 833 | auto result_stream_id = ExchangeStreamId(final_result_exchange.name, 0, 0); |
| 834 | |
| 835 | /// Add a step that writes the result of the main stage to the file |
| 836 | auto & main_stage = distributed_plan.stages["main"]; |
| 837 | if (!main_stage.query_plan_fragment.isCompleted()) |
| 838 | { |
| 839 | main_stage.query_plan_fragment.addStep(std::make_unique<GatherSendStep>(result_header, final_result_exchange.name)); |
| 840 | main_stage.tasks.front().output_exchange_streams.emplace_back(result_stream_id); |
| 841 | distributed_plan.exchange_descriptions[final_result_exchange.name] = final_result_exchange; |
| 842 | distributed_plan.final_result_stream_name = result_stream_id.toString(); |
| 843 | } |
| 844 | |
| 845 | /// Fail early (before execution) if any fragment contains a step that cannot be serialized |
| 846 | /// for remote execution, instead of throwing late from serializeQueryPlan. |
| 847 | for (const auto & [stage_name, stage] : distributed_plan.stages) |
| 848 | assertFragmentSerializable(stage.query_plan_fragment, stage_name); |
| 849 | |
| 850 | /// Collect the list of all temporary files |
| 851 | Strings all_temporary_files_for_cleanup; |
nothing calls this directly
no test coverage detected