public static String printLogical(List<PlanFragment> fragments, FunctionAndTypeManager functionAndTypeManager, Session session)
    {
        Map<PlanFragmentId, PlanFragment> fragmentsById = Maps.uniqueIndex(fragments, PlanFragment::getId);
        PlanNodeIdGenerator idGenerator = new PlanNodeIdGenerator();

        StringBuilder output = new StringBuilder();
        output.append("digraph logical_plan {\n");

        for (PlanFragment fragment : fragments) {
            printFragmentNodes(output, fragment, idGenerator, functionAndTypeManager, session);
        }

        for (PlanFragment fragment : fragments) {
            fragment.getRoot().accept(new EdgePrinter(output, fragmentsById, idGenerator), null);
        }

        output.append("}\n");

        return output.toString();
    }
@Test
    public void testPrintLogicalForJoinNode()
    {
        ValuesNode valuesNode = new ValuesNode(Optional.empty(), new PlanNodeId("right"), ImmutableList.of(), ImmutableList.of(), Optional.empty());

        PlanNode node = new JoinNode(
                Optional.empty(),
                new PlanNodeId("join"),
                JoinType.INNER,
                TEST_TABLE_SCAN_NODE, //Left : Probe side
                valuesNode, //Right : Build side
                Collections.emptyList(), //No Criteria
                ImmutableList.<VariableReferenceExpression>builder()
                        .addAll(TEST_TABLE_SCAN_NODE.getOutputVariables())
                        .addAll(valuesNode.getOutputVariables())
                        .build(),
                Optional.empty(), //NO filter
                Optional.empty(),
                Optional.empty(),
                Optional.of(JoinDistributionType.REPLICATED),
                ImmutableMap.of());

        String actual = printLogical(
                ImmutableList.of(createTestPlanFragment(0, node)),
                FUNCTION_AND_TYPE_MANAGER,
                testSessionBuilder().build());

        String expected = "digraph logical_plan {\n" +
                "subgraph cluster_0 {\n" +
                "label = \"SOURCE\"\n" +
                "plannode_1[label=\"{CrossJoin[REPLICATED]|Estimates: \\{rows: ? (0B), cpu: ?, memory: ?, network: ?\\}\n" +
                "}\", style=\"rounded, filled\", shape=record, fillcolor=orange];\n" +
                "plannode_2[label=\"{TableScan | [TableHandle \\{connectorId='connector_id', connectorHandle='com.facebook.presto.testing.TestingMetadata$TestingTableHandle@1af56f7', layout='Optional.empty'\\}]|Estimates: \\{rows: ? (0B), cpu: ?, memory: ?, network: ?\\}\n" +
                "}\", style=\"rounded, filled\", shape=record, fillcolor=deepskyblue];\n" +
                "plannode_3[label=\"{Values|Estimates: \\{rows: ? (0B), cpu: ?, memory: ?, network: ?\\}\n" +
                "}\", style=\"rounded, filled\", shape=record, fillcolor=deepskyblue];\n" +
                "}\n" +
                "plannode_1 -> plannode_3 [label = \"Build\"];\n" + //valuesNode should be the Build side
                "plannode_1 -> plannode_2 [label = \"Probe\"];\n" + //TEST_TABLE_SCAN_NODE should be the Probe side
                "}\n";

        assertEquals(actual, expected);
    }