private void toString(StringBuilder builder, int indent) {
    checkState(matchers.stream().filter(PlanNodeMatcher.class::isInstance).count() <= 1);

    builder.append(indentString(indent)).append("- ");
    if (anyTree) {
      builder.append("anyTree");
    } else {
      builder.append("node");
    }

    Optional<PlanNodeMatcher> planNodeMatcher =
        matchers.stream()
            .filter(PlanNodeMatcher.class::isInstance)
            .map(PlanNodeMatcher.class::cast)
            .findFirst();

    planNodeMatcher.ifPresent(
        nodeMatcher ->
            builder.append("(").append(nodeMatcher.getNodeClass().getSimpleName()).append(")"));

    builder.append("\n");

    List<Matcher> matchersToPrint =
        matchers.stream()
            .filter(matcher -> !(matcher instanceof PlanNodeMatcher))
            .collect(toImmutableList());

    for (Matcher matcher : matchersToPrint) {
      builder
          .append(indentString(indent + 1))
          .append(matcher.toString().replace("\n", "\n" + indentString(indent + 1)))
          .append("\n");
    }

    for (PlanMatchPattern pattern : sourcePatterns) {
      pattern.toString(builder, indent + 1);
    }
  }
Matches to any tree of nodes with children matching to given source matchers.
anyTree(tableScan("nation")) - will match to any plan which all leafs contain any node
containing table scan from nation table.

<p>Note: anyTree does not match zero nodes. E.g. output(anyTree(tableScan)) will NOT match
TableScan node followed by OutputNode.
iotdb-core/datanode/src/test/java/org/apache/iotdb/db/queryengine/plan/relational/planner/assertions/PlanMatchPattern.java