Repository: alibaba/GraphScope (language: python)

Issue:
[BUG] Unexpected when dealing with filters in `match` in the GOpt-based compilation
**Describe the bug**

In GOpt-based compilation, the following query throws exceptions:
Query:
```
gremlin> g.V().match(__.as("a").out("KNOWS").as("b"), __.as("b").hasLabel("PERSON").has("gender", "male").out("KNOWS").as("c")).count()
filters =(_.gender, _UTF-8'male') not exist in the previous vertex filters null
Type ':help' or ':h' for help.
```
The detailed exception:
```
java.lang.IllegalArgumentException: filters =(_.gender, _UTF-8'male') not exist in the previous vertex filters null
        at com.alibaba.graphscope.common.ir.planner.GraphIOProcessor$InputConvertor$1.visitAndAddVertex(GraphIOProcessor.java:276)
        at com.alibaba.graphscope.common.ir.planner.GraphIOProcessor$InputConvertor$1.visit(GraphIOProcessor.java:202)
        at org.apache.calcite.rel.AbstractRelNode.childrenAccept(AbstractRelNode.java:215)
        at org.apache.calcite.rel.RelVisitor.visit(RelVisitor.java:46)
        at com.alibaba.graphscope.common.ir.planner.GraphIOProcessor$InputConvertor$1.visit(GraphIOProcessor.java:199)
        at org.apache.calcite.rel.AbstractRelNode.childrenAccept(AbstractRelNode.java:215)
        at org.apache.calcite.rel.RelVisitor.visit(RelVisitor.java:46)
        at com.alibaba.graphscope.common.ir.planner.GraphIOProcessor$InputConvertor$1.visit(GraphIOProcessor.java:199)
        at org.apache.calcite.rel.RelVisitor.go(RelVisitor.java:63)
        at com.alibaba.graphscope.common.ir.planner.GraphIOProcessor$InputConvertor.visit(GraphIOProcessor.java:390)
        at com.alibaba.graphscope.common.ir.planner.GraphIOProcessor$InputConvertor.visit(GraphIOProcessor.java:144)
        at com.alibaba.graphscope.common.ir.rel.graph.match.GraphLogicalMultiMatch.accept(GraphLogicalMultiMatch.java:100)
        at com.alibaba.graphscope.common.ir.planner.GraphIOProcessor.processInput(GraphIOProcessor.java:98)
        at com.alibaba.graphscope.common.ir.planner.GraphIOProcessor.processInput(GraphIOProcessor.java:91)
        at com.alibaba.graphscope.common.ir.planner.GraphRelOptimizer$MatchOptimizer.visit(GraphRelOptimizer.java:135)
        at com.alibaba.graphscope.common.ir.rel.graph.match.GraphLogicalMultiMatch.accept(GraphLogicalMultiMatch.java:100)
        at org.apache.calcite.rel.RelShuttleImpl.visitChild(RelShuttleImpl.java:57)
        at org.apache.calcite.rel.RelShuttleImpl.visitChildren(RelShuttleImpl.java:71)
        at com.alibaba.graphscope.common.ir.rel.GraphShuttle.visit(GraphShuttle.java:65)
        at com.alibaba.graphscope.common.ir.rel.GraphLogicalAggregate.accept(GraphLogicalAggregate.java:138)
        at com.alibaba.graphscope.common.ir.planner.GraphRelOptimizer.optimize(GraphRelOptimizer.java:107)
        at com.alibaba.graphscope.common.ir.tools.GraphPlanner$PlannerInstance.planLogical(GraphPlanner.java:118)
        at com.alibaba.graphscope.common.ir.tools.QueryCache$Key.<init>(QueryCache.java:62)
        at com.alibaba.graphscope.common.ir.tools.QueryCache.createKey(QueryCache.java:80)
        at com.alibaba.graphscope.gremlin.plugin.script.GremlinCalciteScriptEngineFactory$ScriptEngine.eval(GremlinCalciteScriptEngineFactory.java:72)
        at java.scripting/javax.script.AbstractScriptEngine.eval(AbstractScriptEngine.java:233)
        at org.apache.tinkerpop.gremlin.groovy.engine.GremlinExecutor.lambda$eval$0(GremlinExecutor.java:272)
        at java.base/java.util.concurrent.FutureTask.run(FutureTask.java:264)
        at java.base/java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:515)
        at java.base/java.util.concurrent.FutureTask.run(FutureTask.java:264)
        at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1128)
        at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:628)
        at java.base/java.lang.Thread.run(Thread.java:829)
```



Discussion / hints:

Fix patch:
diff --git a/interactive_engine/compiler/src/main/java/com/alibaba/graphscope/common/ir/planner/GraphIOProcessor.java b/interactive_engine/compiler/src/main/java/com/alibaba/graphscope/common/ir/planner/GraphIOProcessor.java
index f630e3fbb13a..599f8004a4ef 100644
--- a/interactive_engine/compiler/src/main/java/com/alibaba/graphscope/common/ir/planner/GraphIOProcessor.java
+++ b/interactive_engine/compiler/src/main/java/com/alibaba/graphscope/common/ir/planner/GraphIOProcessor.java
@@ -37,6 +37,7 @@
 import com.alibaba.graphscope.common.ir.tools.Utils;
 import com.alibaba.graphscope.common.ir.tools.config.*;
 import com.alibaba.graphscope.common.ir.type.GraphLabelType;
+import com.alibaba.graphscope.common.ir.type.GraphPathType;
 import com.alibaba.graphscope.common.ir.type.GraphSchemaType;
 import com.alibaba.graphscope.common.store.IrMeta;
 import com.alibaba.graphscope.groot.common.schema.api.EdgeRelation;
@@ -340,10 +341,21 @@ private PatternEdge visitAndAddPxdEdge(
                                             : ((RexLiteral) pxd.getFetch())
                                                     .getValueAs(Number.class)
                                                     .intValue();
+                            GraphPathType pathType =
+                                    (GraphPathType)
+                                            pxd.getRowType().getFieldList().get(0).getType();
+                            GraphLabelType labelType =
+                                    ((GraphSchemaType) pathType.getComponentType().getGetVType())
+                                            .getLabelType();
+                            List<Integer> innerGetVTypes =
+                                    labelType.getLabelsEntry().stream()
+                                            .map(k -> k.getLabelId())
+                                            .collect(Collectors.toList());
                             ElementDetails newDetails =
                                     new ElementDetails(
                                             expandEdge.getElementDetails().getSelectivity(),
-                                            new PathExpandRange(offset, fetch));
+                                            new PathExpandRange(offset, fetch),
+                                            innerGetVTypes);
                             expandEdge =
                                     (expandEdge instanceof SinglePatternEdge)
                                             ? new SinglePatternEdge(
@@ -442,16 +454,18 @@ private void checkPattern(Pattern pattern) {
                                         expectedDstIds.add(k.getSrcLabelId());
                                     }
                                 });
-                Preconditions.checkArgument(
-                        Sets.newHashSet(src.getVertexTypeIds()).equals(expectedSrcIds),
-                        "src vertex types %s not consistent with edge types %s",
-                        src.getVertexTypeIds(),
-                        edge.getEdgeTypeIds());
-                Preconditions.checkArgument(
-                        Sets.newHashSet(dst.getVertexTypeIds()).equals(expectedDstIds),
-                        "dst vertex types %s not consistent with edge types %s",
-                        dst.getVertexTypeIds(),
-                        edge.getEdgeTypeIds());
+                if (edge.getElementDetails().getRange() == null) {
+                    Preconditions.checkArgument(
+                            Sets.newHashSet(src.getVertexTypeIds()).equals(expectedSrcIds),
+                            "src vertex types %s not consistent with edge types %s",
+                            src.getVertexTypeIds(),
+                            edge.getEdgeTypeIds());
+                    Preconditions.checkArgument(
+                            Sets.newHashSet(dst.getVertexTypeIds()).equals(expectedDstIds),
+                            "dst vertex types %s not consistent with edge types %s",
+                            dst.getVertexTypeIds(),
+                            edge.getEdgeTypeIds());
+                }
             }
         }
     }
@@ -776,8 +790,13 @@ private RelNode createExpandGetV(
                 if (edgeValue.getFilter() != null) {
                     pxdBuilder.filter(edgeValue.getFilter());
                 }
+                GetVConfig innerGetVConfig =
+                        new GetVConfig(
+                                getVConfig.getOpt(),
+                                createLabels(edge.getElementDetails().getPxdInnerGetVTypes(), true),
+                                getVConfig.getAlias());
                 pxdBuilder
-                        .getV(getVConfig)
+                        .getV(innerGetVConfig)
                         .resultOpt(GraphOpt.PathExpandResult.END_V)
                         .pathOpt(GraphOpt.PathExpandPath.ARBITRARY)
                         .alias(edgeValue.getAlias())
diff --git a/interactive_engine/compiler/src/main/java/com/alibaba/graphscope/common/ir/planner/rules/JoinDecompositionRule.java b/interactive_engine/compiler/src/main/java/com/alibaba/graphscope/common/ir/planner/rules/JoinDecompositionRule.java
index d97102c83170..1e02d5041cf2 100644
--- a/interactive_engine/compiler/src/main/java/com/alibaba/graphscope/common/ir/planner/rules/JoinDecompositionRule.java
+++ b/interactive_engine/compiler/src/main/java/com/alibaba/graphscope/common/ir/planner/rules/JoinDecompositionRule.java
@@ -380,7 +380,10 @@ private PatternEdge createNewEdge(
         // details.
         int newEdgeId = oldEdge.getId();
         ElementDetails newDetails =
-                new ElementDetails(oldEdge.getElementDetails().getSelectivity(), newRange);
+                new ElementDetails(
+                        oldEdge.getElementDetails().getSelectivity(),
+                        newRange,
+                        oldEdge.getElementDetails().getPxdInnerGetVTypes());
         return (oldEdge instanceof SinglePatternEdge)
                 ? new SinglePatternEdge(
                         newSrc,
diff --git a/interactive_engine/compiler/src/main/java/com/alibaba/graphscope/common/ir/rel/graph/GraphLogicalPathExpand.java b/interactive_engine/compiler/src/main/java/com/alibaba/graphscope/common/ir/rel/graph/GraphLogicalPathExpand.java
index d4327c64e8f1..68fed08477f5 100644
--- a/interactive_engine/compiler/src/main/java/com/alibaba/graphscope/common/ir/rel/graph/GraphLogicalPathExpand.java
+++ b/interactive_engine/compiler/src/main/java/com/alibaba/graphscope/common/ir/rel/graph/GraphLogicalPathExpand.java
@@ -37,7 +37,6 @@
 import org.apache.calcite.rel.type.RelRecordType;
 import org.apache.calcite.rex.RexNode;
 import org.apache.calcite.sql.SqlExplainLevel;
-import org.apache.commons.lang3.ObjectUtils;
 import org.checkerframework.checker.nullness.qual.Nullable;
 
 import java.util.List;
@@ -318,27 +317,27 @@ protected RelDataType deriveRowType() {
     }
 
     private GraphPathType.ElementType getElementType() {
-        switch (resultOpt) {
-            case ALL_V:
-            case END_V:
-                RelNode getV = this.fused != null ? this.fused : this.getV;
-                ObjectUtils.requireNonEmpty(
-                        getV.getRowType().getFieldList(),
-                        "data type of getV operator should have at least one column field");
-                return new GraphPathType.ElementType(
-                        getV.getRowType().getFieldList().get(0).getType());
-            case ALL_V_E:
-            default:
-                ObjectUtils.requireNonEmpty(
-                        this.expand.getRowType().getFieldList(),
-                        "data type of expand operator should have at least one column field");
-                ObjectUtils.requireNonEmpty(
-                        this.getV.getRowType().getFieldList(),
-                        "data type of getV operator should have at least one column field");
-                return new GraphPathType.ElementType(
-                        this.expand.getRowType().getFieldList().get(0).getType(),
-                        this.getV.getRowType().getFieldList().get(0).getType());
+        GraphLogicalExpand innerExpand = null;
+        GraphLogicalGetV innerGetV = null;
+        if (this.fused instanceof GraphPhysicalGetV) {
+            innerExpand = ((GraphPhysicalExpand) this.fused.getInput(0)).getFusedExpand();
+            innerGetV = ((GraphPhysicalGetV) this.fused).getFusedGetV();
+        } else if (this.fused instanceof GraphPhysicalExpand) {
+            innerExpand = ((GraphPhysicalExpand) this.fused).getFusedExpand();
+            innerGetV = ((GraphPhysicalExpand) this.fused).getFusedGetV();
+        } else if (this.expand != null && this.getV != null) {
+            innerExpand = (GraphLogicalExpand) this.expand;
+            innerGetV = (GraphLogicalGetV) this.getV;
         }
+        Preconditions.checkArgument(
+                innerExpand != null && !innerExpand.getRowType().getFieldList().isEmpty(),
+                "data type of expand operator should have at least one column field");
+        Preconditions.checkArgument(
+                innerGetV != null && !innerGetV.getRowType().getFieldList().isEmpty(),
+                "data type of getV operator should have at least one column field");
+        return new GraphPathType.ElementType(
+                innerExpand.getRowType().getFieldList().get(0).getType(),
+                innerGetV.getRowType().getFieldList().get(0).getType());
     }
 
     @Override
diff --git a/interactive_engine/compiler/src/main/java/com/alibaba/graphscope/common/ir/rel/graph/GraphPhysicalExpand.java b/interactive_engine/compiler/src/main/java/com/alibaba/graphscope/common/ir/rel/graph/GraphPhysicalExpand.java
index c725a1577833..d33b89d128ab 100644
--- a/interactive_engine/compiler/src/main/java/com/alibaba/graphscope/common/ir/rel/graph/GraphPhysicalExpand.java
+++ b/interactive_engine/compiler/src/main/java/com/alibaba/graphscope/common/ir/rel/graph/GraphPhysicalExpand.java
@@ -197,4 +197,8 @@ public RelNode accept(RelShuttle shuttle) {
         }
         return shuttle.visit(this);
     }
+
+    public GraphLogicalGetV getFusedGetV() {
+        return fusedGetV;
+    }
 }
diff --git a/interactive_engine/compiler/src/main/java/com/alibaba/graphscope/common/ir/rel/metadata/glogue/pattern/ElementDetails.java b/interactive_engine/compiler/src/main/java/com/alibaba/graphscope/common/ir/rel/metadata/glogue/pattern/ElementDetails.java
index 159229d680d7..d219ddd8e5bc 100644
--- a/interactive_engine/compiler/src/main/java/com/alibaba/graphscope/common/ir/rel/metadata/glogue/pattern/ElementDetails.java
+++ b/interactive_engine/compiler/src/main/java/com/alibaba/graphscope/common/ir/rel/metadata/glogue/pattern/ElementDetails.java
@@ -16,14 +16,20 @@
 
 package com.alibaba.graphscope.common.ir.rel.metadata.glogue.pattern;
 
+import com.google.common.collect.ImmutableList;
+
 import org.checkerframework.checker.nullness.qual.Nullable;
 
+import java.util.Collections;
+import java.util.List;
 import java.util.Objects;
 
 public class ElementDetails implements Comparable<ElementDetails> {
     private final double selectivity;
     // the range is not null if and only if the element denotes a path expand operator
     private final @Nullable PathExpandRange range;
+    // record inner getV types of path expand
+    private final List<Integer> pxdInnerGetVTypes;
     private boolean optional;
 
     public ElementDetails() {
@@ -31,20 +37,26 @@ public ElementDetails() {
     }
 
     public ElementDetails(double selectivity) {
-        this(selectivity, null);
+        this(selectivity, null, ImmutableList.of());
     }
 
-    public ElementDetails(double selectivity, @Nullable PathExpandRange range) {
-        this(selectivity, range, false);
+    public ElementDetails(
+            double selectivity, @Nullable PathExpandRange range, List<Integer> pxdInnerGetVTypes) {
+        this(selectivity, range, pxdInnerGetVTypes, false);
     }
 
     public ElementDetails(double selectivity, boolean optional) {
-        this(selectivity, null, optional);
+        this(selectivity, null, ImmutableList.of(), optional);
     }
 
-    public ElementDetails(double selectivity, @Nullable PathExpandRange range, boolean optional) {
+    public ElementDetails(
+            double selectivity,
+            @Nullable PathExpandRange range,
+            List<Integer> pxdInnerVertexTypes,
+            boolean optional) {
         this.selectivity = selectivity;
         this.range = range;
+        this.pxdInnerGetVTypes = pxdInnerVertexTypes;
         this.optional = optional;
     }
 
@@ -55,6 +67,7 @@ public boolean equals(Object o) {
         ElementDetails details = (ElementDetails) o;
         return Double.compare(details.selectivity, selectivity) == 0
                 && Objects.equals(range, details.range)
+                && Objects.equals(pxdInnerGetVTypes, details.pxdInnerGetVTypes)
                 && optional == details.optional;
     }
 
@@ -79,6 +92,10 @@ public void setOptional(boolean optional) {
         this.optional = optional;
     }
 
+    public List<Integer> getPxdInnerGetVTypes() {
+        return Collections.unmodifiableList(this.pxdInnerGetVTypes);
+    }
+
     @Override
     public int compareTo(ElementDetails o) {
         int compare = Double.compare(this.selectivity, o.selectivity);
@@ -89,6 +106,9 @@ public int compareTo(ElementDetails o) {
         if (compare != 0) {
             return compare;
         }
+        if (!this.pxdInnerGetVTypes.equals(o.pxdInnerGetVTypes)) {
+            return -1;
+        }
         if (this.range != null && o.range != null) {
             return this.range.compareTo(o.range);
         }
diff --git a/interactive_engine/compiler/src/main/java/com/alibaba/graphscope/common/ir/type/GraphPathType.java b/interactive_engine/compiler/src/main/java/com/alibaba/graphscope/common/ir/type/GraphPathType.java
index 7669c6e06860..e06a577efdf5 100644
--- a/interactive_engine/compiler/src/main/java/com/alibaba/graphscope/common/ir/type/GraphPathType.java
+++ b/interactive_engine/compiler/src/main/java/com/alibaba/graphscope/common/ir/type/GraphPathType.java
@@ -22,8 +22,6 @@
 import org.apache.calcite.sql.type.SqlTypeName;
 import org.checkerframework.checker.nullness.qual.Nullable;
 
-import java.util.Objects;
-
 public class GraphPathType extends ArraySqlType {
     public GraphPathType(ElementType elementType) {
         this(elementType, false);
@@ -52,7 +50,7 @@ public ElementType(RelDataType getVType) {
         public ElementType(RelDataType expandType, RelDataType getVType) {
             super(SqlTypeName.OTHER, false, null);
             this.expandType = expandType;
-            this.getVType = Objects.requireNonNull(getVType);
+            this.getVType = getVType;
             computeDigest();
         }
 
diff --git a/interactive_engine/compiler/src/main/java/com/alibaba/graphscope/common/ir/type/GraphTypeInference.java b/interactive_engine/compiler/src/main/java/com/alibaba/graphscope/common/ir/type/GraphTypeInference.java
index 88808f550778..083257b6b6b6 100644
--- a/interactive_engine/compiler/src/main/java/com/alibaba/graphscope/common/ir/type/GraphTypeInference.java
+++ b/interactive_engine/compiler/src/main/java/com/alibaba/graphscope/common/ir/type/GraphTypeInference.java
@@ -32,6 +32,7 @@
 import org.apache.calcite.plan.RelOptTable;
 import org.apache.calcite.rel.RelNode;
 import org.apache.calcite.rel.type.RelDataType;
+import org.apache.calcite.rex.RexLiteral;
 import org.apache.calcite.rex.RexNode;
 import org.apache.commons.lang3.ObjectUtils;
 import org.checkerframework.checker.nullness.qual.Nullable;
@@ -164,17 +165,38 @@ private RelDataType restrictChild(
         if (child instanceof GraphLogicalSource
                         && ((GraphLogicalSource) child).getOpt() == GraphOpt.Source.VERTEX
                 || child instanceof GraphLogicalGetV) {
+            GraphSchemaType childType = (GraphSchemaType) getType(child);
+            GraphLabelType childLabelType = childType.getLabelType();
             if (parent instanceof GraphLogicalPathExpand) {
-                parent = ((GraphLogicalPathExpand) parent).getExpand();
-                parentType = ((GraphPathType) parentType).getComponentType().getExpandType();
+                GraphLogicalPathExpand pxd = (GraphLogicalPathExpand) parent;
+                int minHop =
+                        (pxd.getOffset() == null)
+                                ? 0
+                                : ((Number) ((RexLiteral) pxd.getOffset()).getValue()).intValue();
+                int maxHop =
+                        pxd.getFetch() == null
+                                ? Integer.MAX_VALUE
+                                : ((Number) ((RexLiteral) pxd.getFetch()).getValue()).intValue()
+                                        + minHop
+                                        - 1;
+                GraphPathTypeInference pathTypeInfer =
+                        new GraphPathTypeInference(
+                                childLabelType,
+                                null,
+                                (GraphPathType) parentType,
+                                ((GraphLogicalExpand) pxd.getExpand()).getOpt(),
+                                minHop,
+                                maxHop);
+                return createSchemaType(
+                        GraphOpt.Source.VERTEX,
+                        pathTypeInfer.inferStartVType().getLabelsEntry(),
+                        childType);
             }
             if (parent instanceof GraphLogicalExpand) {
                 GraphLogicalExpand expand = (GraphLogicalExpand) parent;
-                GraphSchemaType childType = (GraphSchemaType) getType(child);
-                GraphLabelType childLabelType = childType.getLabelType();
                 GraphLabelType parentLabelType = ((GraphSchemaType) parentType).getLabelType();
                 List<GraphLabelType.Entry> commonLabels =
-                        commonLabels(childLabelType, parentLabelType, expand.getOpt(), true);
+                        commonLabels(childLabelType, parentLabelType, expand.getOpt(), true, false);
                 return createSchemaType(GraphOpt.Source.VERTEX, commonLabels, childType);
             }
             throw new IllegalArgumentException(
@@ -220,22 +242,25 @@ private RelDataType restrictChild(
                     child,
                     parent);
             GraphLogicalPathExpand pxd = (GraphLogicalPathExpand) child;
-            RelDataType innerGetVType =
-                    ((GraphPathType) getType(pxd)).getComponentType().getGetVType();
-            GraphLabelType innerGetVLabelType = ((GraphSchemaType) innerGetVType).getLabelType();
-            GraphLabelType outerGetVLabelType = ((GraphSchemaType) parentType).getLabelType();
-            List<GraphLabelType.Entry> commonLabels =
-                    commonLabels(innerGetVLabelType, outerGetVLabelType);
-            RelDataType newGetVType =
-                    createSchemaType(
-                            GraphOpt.Source.VERTEX, commonLabels, (GraphSchemaType) innerGetVType);
-            RelDataType newExpandType =
-                    restrictChild(
-                            relGraph,
-                            pxd.getExpand().copy(pxd.getTraitSet(), pxd.getInputs()),
-                            pxd.getGetV(),
-                            newGetVType);
-            return new GraphPathType(new GraphPathType.ElementType(newExpandType, newGetVType));
+            int minHop =
+                    (pxd.getOffset() == null)
+                            ? 0
+                            : ((Number) ((RexLiteral) pxd.getOffset()).getValue()).intValue();
+            int maxHop =
+                    pxd.getFetch() == null
+                            ? Integer.MAX_VALUE
+                            : ((Number) ((RexLiteral) pxd.getFetch()).getValue()).intValue()
+                                    + minHop
+                                    - 1;
+            GraphPathTypeInference pathTypeInfer =
+                    new GraphPathTypeInference(
+                            ((GraphSchemaType) getType(child.getInput(0))).getLabelType(),
+                            ((GraphSchemaType) parentType).getLabelType(),
+                            (GraphPathType) getType(child),
+                            ((GraphLogicalExpand) pxd.getExpand()).getOpt(),
+                            minHop,
+                            maxHop);
+            return pathTypeInfer.inferPathType();
         }
         throw new IllegalArgumentException(
                 "graph generic type error: unable to establish an extension relationship between"
@@ -263,29 +288,32 @@ private RelDataType restrictParent(
                 GraphLabelType childLabelType = ((GraphSchemaType) getType(child)).getLabelType();
                 GraphLabelType parentLabelType = ((GraphSchemaType) parentType).getLabelType();
                 List<GraphLabelType.Entry> commonLabels =
-                        commonLabels(childLabelType, parentLabelType, expand.getOpt(), false);
+                        commonLabels(
+                                childLabelType, parentLabelType, expand.getOpt(), false, false);
                 return createSchemaType(
                         GraphOpt.Source.EDGE, commonLabels, (GraphSchemaType) parentType);
             }
             if (parent instanceof GraphLogicalPathExpand) {
                 GraphLogicalPathExpand pxd = (GraphLogicalPathExpand) parent;
-                RelDataType newExpandType =
-                        restrictParent(
-                                relGraph,
-                                child,
-                                pxd.getExpand(),
-                                ((GraphPathType) parentType).getComponentType().getExpandType());
-                RelNode oldExpand =
-                        pxd.getExpand()
-                                .copy(pxd.getExpand().getTraitSet(), ImmutableList.of(child));
-                RelNode newExpand = newRel(oldExpand, newExpandType);
-                RelDataType newGetVType =
-                        restrictParent(
-                                relGraph,
-                                newExpand,
-                                pxd.getGetV(),
-                                ((GraphPathType) parentType).getComponentType().getGetVType());
-                return new GraphPathType(new GraphPathType.ElementType(newExpandType, newGetVType));
+                int minHop =
+                        (pxd.getOffset() == null)
+                                ? 0
+                                : ((Number) ((RexLiteral) pxd.getOffset()).getValue()).intValue();
+                int maxHop =
+                        pxd.getFetch() == null
+                                ? Integer.MAX_VALUE
+                                : ((Number) ((RexLiteral) pxd.getFetch()).getValue()).intValue()
+                                        + minHop
+                                        - 1;
+                GraphPathTypeInference pathTypeInfer =
+                        new GraphPathTypeInference(
+                                ((GraphSchemaType) getType(child)).getLabelType(),
+                                ((GraphSchemaType) getType(pxd.getGetV())).getLabelType(),
+                                (GraphPathType) parentType,
+                                ((GraphLogicalExpand) pxd.getExpand()).getOpt(),
+                                minHop,
+                                maxHop);
+                return pathTypeInfer.inferPathType();
             }
             throw new IllegalArgumentException(
                     "graph generic type error: unable to establish an extension relationship"
@@ -329,15 +357,30 @@ private RelDataType restrictParent(
                             + " between node %s with node %s",
                     child,
                     parent);
-            GraphLogicalPathExpand pxd = (GraphLogicalPathExpand) child;
-            RelDataType innerGetVType =
-                    ((GraphPathType) getType(pxd)).getComponentType().getGetVType();
-            GraphLabelType innerGetVLabelType = ((GraphSchemaType) innerGetVType).getLabelType();
             GraphLabelType outerGetVLabelType = ((GraphSchemaType) parentType).getLabelType();
-            List<GraphLabelType.Entry> commonLabels =
-                    commonLabels(innerGetVLabelType, outerGetVLabelType);
+            GraphLogicalPathExpand pxd = (GraphLogicalPathExpand) child;
+            int minHop =
+                    (pxd.getOffset() == null)
+                            ? 0
+                            : ((Number) ((RexLiteral) pxd.getOffset()).getValue()).intValue();
+            int maxHop =
+                    pxd.getFetch() == null
+                            ? Integer.MAX_VALUE
+                            : ((Number) ((RexLiteral) pxd.getFetch()).getValue()).intValue()
+                                    + minHop
+                                    - 1;
+            GraphPathTypeInference pathTypeInfer =
+                    new GraphPathTypeInference(
+                            null,
+                            outerGetVLabelType,
+                            (GraphPathType) getType(pxd),
+                            ((GraphLogicalExpand) pxd.getExpand()).getOpt(),
+                            minHop,
+                            maxHop);
             return createSchemaType(
-                    GraphOpt.Source.VERTEX, commonLabels, (GraphSchemaType) parentType);
+                    GraphOpt.Source.VERTEX,
+                    pathTypeInfer.inferGetVType().getLabelsEntry(),
+                    (GraphSchemaType) parentType);
         }
         throw new IllegalArgumentException(
                 "graph generic type error: unable to establish an extension relationship between"
@@ -483,7 +526,8 @@ private List<GraphLabelType.Entry> commonLabels(
             GraphLabelType getVType,
             GraphLabelType expandType,
             GraphOpt.Expand expandOpt,
-            boolean recordGetV) {
+            boolean recordGetV,
+            boolean containsZeroPath) {
         List<GraphLabelType.Entry> commonLabels = Lists.newArrayList();
         for (GraphLabelType.Entry entry1 : getVType.getLabelsEntry()) {
             for (GraphLabelType.Entry entry2 : expandType.getLabelsEntry()) {
@@ -495,6 +539,11 @@ private List<GraphLabelType.Entry> commonLabels(
                             commonLabels.add(entry2);
                         }
                     }
+                    if (containsZeroPath
+                            && recordGetV
+                            && entry1.getLabel().equals(entry2.getSrcLabel())) {
+                        commonLabels.add(entry1);
+                    }
                 }
                 if (expandOpt != GraphOpt.Expand.IN) {
                     if (entry1.getLabel().equals(entry2.getSrcLabel())) {
@@ -504,6 +553,11 @@ private List<GraphLabelType.Entry> commonLabels(
                             commonLabels.add(entry2);
                         }
                     }
+                    if (containsZeroPath
+                            && recordGetV
+                            && entry1.getLabel().equals(entry2.getDstLabel())) {
+                        commonLabels.add(entry1);
+                    }
                 }
             }
         }
@@ -749,4 +803,225 @@ public List<RelNode> getRels() {
             return null;
         }
     }
+
+    private class GraphPathTypeInference {
+        private final GraphLabelType startVType;
+        private final GraphLabelType endVType;
+        private final GraphPathType pxdType;
+        private final GraphOpt.Expand expandOpt;
+        private final List<CompositePathType> allValidPathTypes;
+        private final int minHop;
+        private final int maxHop;
+
+        public GraphPathTypeInference(
+                GraphLabelType startVType,
+                GraphLabelType endVType,
+                GraphPathType pxdType,
+                GraphOpt.Expand expandOpt,
+                int minHop,
+                int maxHop) {
+            this.startVType = startVType;
+            this.endVType = endVType;
+            this.pxdType = pxdType;
+            this.expandOpt = expandOpt;
+            this.minHop = minHop;
+            this.maxHop = maxHop;
+            this.allValidPathTypes = Lists.newArrayList();
+        }
+
+        public GraphPathType inferPathType() {
+            recursive(startVType, new CompositePathType(Lists.newArrayList()), 0);
+            List<GraphLabelType.Entry> expandTypes = Lists.newArrayList();
+            List<GraphLabelType.Entry> getVTypes = Lists.newArrayList();
+            allValidPathTypes.forEach(
+                    k -> {
+                        k.getElementTypes()
+                                .forEach(
+                                        k1 -> {
+                                            if (k1.getExpandType() != null) {
+                                                expandTypes.addAll(
+                                                        ((GraphLabelType) k1.getExpandType())
+                                                                .getLabelsEntry());
+                                            }
+                                            if (k1.getGetVType() != null) {
+                                                getVTypes.addAll(
+                                                        ((GraphLabelType) k1.getGetVType())
+                                                                .getLabelsEntry());
+                                            }
+                                        });
+                    });
+            Preconditions.checkArgument(
+                    !expandTypes.isEmpty() && !getVTypes.isEmpty(),
+                    "cannot find any path within hops of [%s, %s] between startV type [%s] and endV"
+                            + " type [%s] with the expand type constraints [%s]",
+                    minHop,
+                    maxHop,
+                    startVType,
+                    endVType,
+                    pxdType);
+            GraphSchemaType expandType =
+                    (GraphSchemaType) pxdType.getComponentType().getExpandType();
+            GraphSchemaType getVType = (GraphSchemaType) pxdType.getComponentType().getGetVType();
+            return new GraphPathType(
+                    new GraphPathType.ElementType(
+                            createSchemaType(
+                                    GraphOpt.Source.EDGE,
+                                    expandTypes.stream().distinct().collect(Collectors.toList()),
+                                    expandType),
+                            createSchemaType(
+                                    GraphOpt.Source.VERTEX,
+                                    getVTypes.stream().distinct().collect(Collectors.toList()),
+                                    getVType)));
+        }
+
+        public GraphLabelType inferStartVType() {
+            GraphLabelType expandType =
+                    ((GraphSchemaType) pxdType.getComponentType().getExpandType()).getLabelType();
+            return new GraphLabelType(
+                    commonLabels(startVType, expandType, expandOpt, true, minHop == 0));
+        }
+
+        public GraphLabelType inferGetVType() {
+            GraphLabelType innerGetVLabelType =
+                    ((GraphSchemaType) pxdType.getComponentType().getGetVType()).getLabelType();
+            List<GraphLabelType.Entry> commonLabels = commonLabels(innerGetVLabelType, endVType);
+            return new GraphLabelType(commonLabels);
+        }
+
+        private void recursive(
+                GraphLabelType curEndVType, CompositePathType curPathType, int curHop) {
+            if (curHop > maxHop) {
+                return;
+            }
+            if (curHop >= minHop && curHop <= maxHop) {
+                List<GraphLabelType.Entry> candidates =
+                        Lists.newArrayList(curEndVType.getLabelsEntry());
+                candidates.retainAll(endVType.getLabelsEntry());
+                if (!candidates.isEmpty()) {
+                    if (curPathType.isEmpty()
+                            || candidates.size() < curEndVType.getLabelsEntry().size()) {
+                        curPathType.setEndVType(new GraphLabelType(candidates));
+                    }
+                    allValidPathTypes.add(curPathType);
+                }
+            }
+            GraphLabelType expandType =
+                    ((GraphSchemaType) pxdType.getComponentType().getExpandType()).getLabelType();
+            curEndVType
+                    .getLabelsEntry()
+                    .forEach(
+                            v -> {
+                                expandType
+                                        .getLabelsEntry()
+                                        .forEach(
+                                                e -> {
+                                                    GraphLabelType nextEndVType = null;
+                                                    switch (expandOpt) {
+                                                        case OUT:
+                                                            if (e.getSrcLabel() == v.getLabel()) {
+                                                                nextEndVType =
+                                                                        new GraphLabelType(
+                                                                                new GraphLabelType
+                                                                                                .Entry()
+                                                                                        .label(
+                                                                                                e
+                                                                                                        .getDstLabel())
+                                                                                        .labelId(
+                                                                                                e
+                                                                                                        .getDstLabelId()));
+                                                            }
+                                                            break;
+                                                        case IN:
+                                                            if (e.getDstLabel() == v.getLabel()) {
+                                                                nextEndVType =
+                                                                        new GraphLabelType(
+                                                                                new GraphLabelType
+                                                                                                .Entry()
+                                                                                        .label(
+                                                                                                e
+                                                                                                        .getSrcLabel())
+                                                                                        .labelId(
+                                                                                                e
+                                                                                                        .getSrcLabelId()));
+                                                            }
+                                                            break;
+                                                        case BOTH:
+                                                            if (e.getSrcLabel() == v.getLabel()) {
+                                                                nextEndVType =
+                                                                        new GraphLabelType(
+                                                                                new GraphLabelType
+                                                                                                .Entry()
+                                                                                        .label(
+                                                                                                e
+                                                                                                        .getDstLabel())
+                                                                                        .labelId(
+                                                                                                e
+                                                                                                        .getDstLabelId()));
+                                                            } else if (e.getDstLabel()
+                                                                    == v.getLabel()) {
+                                                                nextEndVType =
+                                                                        new GraphLabelType(
+                                                                                new GraphLabelType
+                                                                                                .Entry()
+                                                                                        .label(
+                                                                                                e
+                                                                                                        .getSrcLabel())
+                                                                                        .labelId(
+                                                                                                e
+                                                                                                        .getSrcLabelId()));
+                                                            }
+                                                            break;
+                                                    }
+                                                    if (nextEndVType != null) {
+                                                        recursive(
+                                                                nextEndVType,
+                                                                curPathType.copy(
+                                                                        new GraphPathType
+                                                                                .ElementType(
+                                                                                new GraphLabelType(
+                                                                                        e),
+                                                                                nextEndVType)),
+                                                                curHop + 1);
+                                                    }
+                                                });
+                            });
+        }
+
+        private class CompositePathType {
+            private final List<GraphPathType.ElementType> elementTypes;
+
+            public CompositePathType(List<GraphPathType.ElementType> elementTypes) {
+                this.elementTypes = elementTypes;
+            }
+
+            public void setEndVType(GraphLabelType endVType) {
+                if (elementTypes.isEmpty()) {
+                    elementTypes.add(new GraphPathType.ElementType(null, endVType));
+                } else {
+                    GraphPathType.ElementType last = elementTypes.get(elementTypes.size() - 1);
+                    elementTypes.set(
+                            elementTypes.size() - 1,
+                            new GraphPathType.ElementType(last.getExpandType(), endVType));
+                }
+            }
+
+            public void add(GraphPathType.ElementType last) {
+                elementTypes.add(last);
+            }
+
+            public List<GraphPathType.ElementType> getElementTypes() {
+                return elementTypes;
+            }
+
+            public CompositePathType copy(GraphPathType.ElementType addOne) {
+                List<GraphPathType.ElementType> newElementTypes = Lists.newArrayList(elementTypes);
+                newElementTypes.add(addOne);
+                return new CompositePathType(newElementTypes);
+            }
+
+            public boolean isEmpty() {
+                return elementTypes.isEmpty();
+            }
+        }
+    }
 }
diff --git a/interactive_engine/compiler/src/main/java/com/alibaba/graphscope/cypher/integration/suite/simple/SimpleMatchQueries.java b/interactive_engine/compiler/src/main/java/com/alibaba/graphscope/cypher/integration/suite/simple/SimpleMatchQueries.java
index 0655fabdde8b..0425062627d8 100644
--- a/interactive_engine/compiler/src/main/java/com/alibaba/graphscope/cypher/integration/suite/simple/SimpleMatchQueries.java
+++ b/interactive_engine/compiler/src/main/java/com/alibaba/graphscope/cypher/integration/suite/simple/SimpleMatchQueries.java
@@ -168,30 +168,14 @@ public static QueryContext get_simple_match_query_12_test() {
     }
 
     public static QueryContext get_simple_match_query_13_test() {
-        String query =
-                "MATCH(a)-[*1..2]->(b) WITH a.id AS aId, b.id AS bId RETURN aId, bId ORDER BY aId"
-                        + " ASC, bId ASC LIMIT 5;";
-        List<String> expected =
-                Arrays.asList(
-                        "Record<{aId: 0, bId: 3}>",
-                        "Record<{aId: 0, bId: 59}>",
-                        "Record<{aId: 0, bId: 59}>",
-                        "Record<{aId: 0, bId: 291}>",
-                        "Record<{aId: 0, bId: 349}>");
+        String query = "MATCH(a)-[c*1..2]->(b) RETURN COUNT(c) AS pathCnt;";
+        List<String> expected = Arrays.asList("Record<{pathCnt: 1477965}>");
         return new QueryContext(query, expected);
     }
 
     public static QueryContext get_simple_match_query_14_test() {
-        String query =
-                "Match (a)-[c*0..2]->(b) RETURN a.id AS aId, c, b.id AS bId ORDER BY aId"
-                        + " ASC, bId ASC LIMIT 5;";
-        List<String> expected =
-                Arrays.asList(
-                        "Record<{aId: 0, c: path[], bId: 0}>",
-                        "Record<{aId: 0, c: path[], bId: 0}>",
-                        "Record<{aId: 0, c: path[], bId: 0}>",
-                        "Record<{aId: 0, c: path[], bId: 0}>",
-                        "Record<{aId: 0, c: path[], bId: 3}>");
+        String query = "Match (a)-[c*0..2]->(b) RETURN COUNT(c) AS pathCnt;";
+        List<String> expected = Arrays.asList("Record<{pathCnt: 1805553}>");
         return new QueryContext(query, expected);
     }
 
diff --git a/interactive_engine/executor/store/exp_store/data/ldbc_metadata.json b/interactive_engine/executor/store/exp_store/data/ldbc_metadata.json
index cec5cf9dd360..5af369b0a421 100644
--- a/interactive_engine/executor/store/exp_store/data/ldbc_metadata.json
+++ b/interactive_engine/executor/store/exp_store/data/ldbc_metadata.json
@@ -120,7 +120,7 @@
       "src_vertex_type": "TAG",
       "dst_vertex_type": "TAGCLASS"
     },
-    "tagclass_isSubClassOf_tagclass_0_0.csv": {
+    "tagclass_isSubclassOf_tagclass_0_0.csv": {
       "etype": "ISSUBCLASSOF",
       "src_vertex_type": "TAGCLASS",
       "dst_vertex_type": "TAGCLASS"


Test patch:
diff --git a/interactive_engine/compiler/src/test/java/com/alibaba/graphscope/common/ir/planner/cbo/BITest.java b/interactive_engine/compiler/src/test/java/com/alibaba/graphscope/common/ir/planner/cbo/BITest.java
index 479d4514704f..bb37097c34ce 100644
--- a/interactive_engine/compiler/src/test/java/com/alibaba/graphscope/common/ir/planner/cbo/BITest.java
+++ b/interactive_engine/compiler/src/test/java/com/alibaba/graphscope/common/ir/planner/cbo/BITest.java
@@ -180,14 +180,14 @@ public void bi3_test() {
                     + "      GraphPhysicalExpand(tableConfig=[{isAll=false, tables=[HASTYPE]}],"
                     + " alias=[_], startAlias=[PATTERN_VERTEX$11], opt=[OUT],"
                     + " physicalOpt=[VERTEX])\n"
-                    + "        GraphPhysicalExpand(tableConfig=[[EdgeLabel(HASTAG, COMMENT, TAG)]],"
-                    + " alias=[PATTERN_VERTEX$11], startAlias=[message], opt=[OUT],"
-                    + " physicalOpt=[VERTEX])\n"
-                    + "          GraphLogicalGetV(tableConfig=[{isAll=false, tables=[COMMENT]}],"
-                    + " alias=[message], opt=[END])\n"
+                    + "        GraphPhysicalExpand(tableConfig=[[EdgeLabel(HASTAG, COMMENT, TAG),"
+                    + " EdgeLabel(HASTAG, POST, TAG)]], alias=[PATTERN_VERTEX$11],"
+                    + " startAlias=[message], opt=[OUT], physicalOpt=[VERTEX])\n"
+                    + "          GraphLogicalGetV(tableConfig=[{isAll=false, tables=[POST,"
+                    + " COMMENT]}], alias=[message], opt=[END])\n"
                     + "           "
-                    + " GraphLogicalPathExpand(fused=[GraphPhysicalExpand(tableConfig=[[EdgeLabel(REPLYOF,"
-                    + " COMMENT, POST)]], alias=[_], opt=[IN], physicalOpt=[VERTEX])\n"
+                    + " GraphLogicalPathExpand(fused=[GraphPhysicalExpand(tableConfig=[{isAll=false,"
+                    + " tables=[REPLYOF]}], alias=[_], opt=[IN], physicalOpt=[VERTEX])\n"
                     + "], fetch=[6], path_opt=[ARBITRARY], result_opt=[END_V], alias=[_],"
                     + " start_alias=[post])\n"
                     + "              GraphPhysicalExpand(tableConfig=[{isAll=false,"
@@ -408,12 +408,12 @@ public void bi9_test() {
                     + " values=[[{operands=[post], aggFunction=COUNT, alias='threadCnt',"
                     + " distinct=true}, {operands=[msg], aggFunction=COUNT, alias='msgCnt',"
                     + " distinct=true}]])\n"
-                    + "      GraphLogicalGetV(tableConfig=[{isAll=false, tables=[COMMENT]}],"
+                    + "      GraphLogicalGetV(tableConfig=[{isAll=false, tables=[POST, COMMENT]}],"
                     + " alias=[msg], fusedFilter=[[AND(>=(_.creationDate, ?0), <=(_.creationDate,"
                     + " ?1))]], opt=[END])\n"
                     + "       "
-                    + " GraphLogicalPathExpand(fused=[GraphPhysicalExpand(tableConfig=[[EdgeLabel(REPLYOF,"
-                    + " COMMENT, POST)]], alias=[_], opt=[IN], physicalOpt=[VERTEX])\n"
+                    + " GraphLogicalPathExpand(fused=[GraphPhysicalExpand(tableConfig=[{isAll=false,"
+                    + " tables=[REPLYOF]}], alias=[_], opt=[IN], physicalOpt=[VERTEX])\n"
                     + "], fetch=[7], path_opt=[ARBITRARY], result_opt=[END_V], alias=[_],"
                     + " start_alias=[post])\n"
                     + "          GraphPhysicalGetV(tableConfig=[{isAll=false, tables=[POST]}],"
diff --git a/interactive_engine/compiler/src/test/java/com/alibaba/graphscope/common/ir/planner/cbo/STPathTest.java b/interactive_engine/compiler/src/test/java/com/alibaba/graphscope/common/ir/planner/cbo/STPathTest.java
index 91e98da2fc54..97a3c4f5a37b 100644
--- a/interactive_engine/compiler/src/test/java/com/alibaba/graphscope/common/ir/planner/cbo/STPathTest.java
+++ b/interactive_engine/compiler/src/test/java/com/alibaba/graphscope/common/ir/planner/cbo/STPathTest.java
@@ -76,4 +76,35 @@ public void st_path_test() {
                 FileUtils.readJsonFromResource("proto/st_path_test.json"),
                 physicalBuilder.build().explain().trim());
     }
+
+    @Test
+    public void st_path_between_person_and_place() {
+        GraphBuilder builder = Utils.mockGraphBuilder(optimizer, irMeta);
+        RelNode before =
+                com.alibaba.graphscope.cypher.antlr4.Utils.eval(
+                                "Match (a:PERSON{id:933})-[c*2..3]->(b:PLACE{id:999}) Return"
+                                        + " count(a);",
+                                builder)
+                        .build();
+        RelNode after = optimizer.optimize(before, new GraphIOProcessor(builder, irMeta));
+        Assert.assertEquals(
+                "GraphLogicalAggregate(keys=[{variables=[], aliases=[]}], values=[[{operands=[a],"
+                    + " aggFunction=COUNT, alias='$f0', distinct=false}]])\n"
+                    + "  GraphLogicalGetV(tableConfig=[{isAll=false, tables=[PLACE]}], alias=[b],"
+                    + " fusedFilter=[[=(_.id, 999)]], opt=[END])\n"
+                    + "    GraphLogicalPathExpand(expand=[GraphLogicalExpand(tableConfig=[[EdgeLabel(ISPARTOF,"
+                    + " PLACE, PLACE), EdgeLabel(ISLOCATEDIN, PERSON, PLACE), EdgeLabel(KNOWS,"
+                    + " PERSON, PERSON), EdgeLabel(LIKES, PERSON, COMMENT), EdgeLabel(LIKES,"
+                    + " PERSON, POST), EdgeLabel(STUDYAT, PERSON, ORGANISATION), EdgeLabel(WORKAT,"
+                    + " PERSON, ORGANISATION), EdgeLabel(ISLOCATEDIN, COMMENT, PLACE),"
+                    + " EdgeLabel(ISLOCATEDIN, POST, PLACE), EdgeLabel(ISLOCATEDIN, ORGANISATION,"
+                    + " PLACE)]], alias=[_], opt=[OUT])\n"
+                    + "], getV=[GraphLogicalGetV(tableConfig=[{isAll=false, tables=[PERSON, POST,"
+                    + " ORGANISATION, PLACE, COMMENT]}], alias=[_], opt=[END])\n"
+                    + "], offset=[2], fetch=[1], path_opt=[ARBITRARY], result_opt=[ALL_V_E],"
+                    + " alias=[c], start_alias=[a])\n"
+                    + "      GraphLogicalSource(tableConfig=[{isAll=false, tables=[PERSON]}],"
+                    + " alias=[a], opt=[VERTEX], uniqueKeyFilters=[=(_.id, 933)])",
+                after.explain().trim());
+    }
 }
diff --git a/interactive_engine/compiler/src/test/java/com/alibaba/graphscope/cypher/antlr4/MatchTest.java b/interactive_engine/compiler/src/test/java/com/alibaba/graphscope/cypher/antlr4/MatchTest.java
index 249b9c04aa9b..c19d71ab85ea 100644
--- a/interactive_engine/compiler/src/test/java/com/alibaba/graphscope/cypher/antlr4/MatchTest.java
+++ b/interactive_engine/compiler/src/test/java/com/alibaba/graphscope/cypher/antlr4/MatchTest.java
@@ -481,4 +481,27 @@ public void match_23_test() {
                     + "], matchOpt=[INNER])",
                 node.explain().trim());
     }
+
+    // test type inference of path expand, a can reach b through the following two paths: either 0
+    // or 1 edge(s)
+    // 1. (a)-[*0]->(b) -> (a:software)-[*0]->(b:software) or (a:person)-[*0]->(b:person)
+    // 2. (a)-[*1]->(b) -> (a:person)-[:created]->(b:software) or (a:person)-[:knows]->(b:person)
+    @Test
+    public void match_24_test() {
+        RelNode node = Utils.eval("Match (a)-[c*0..2]->(b) Return a").build();
+        Assert.assertEquals(
+                "GraphLogicalProject(a=[a], isAppend=[false])\n"
+                    + "  GraphLogicalSingleMatch(input=[null],"
+                    + " sentence=[GraphLogicalGetV(tableConfig=[{isAll=true, tables=[software,"
+                    + " person]}], alias=[b], opt=[END])\n"
+                    + "  GraphLogicalPathExpand(expand=[GraphLogicalExpand(tableConfig=[{isAll=true,"
+                    + " tables=[created, knows]}], alias=[_], opt=[OUT])\n"
+                    + "], getV=[GraphLogicalGetV(tableConfig=[{isAll=true, tables=[software,"
+                    + " person]}], alias=[_], opt=[END])\n"
+                    + "], fetch=[2], path_opt=[ARBITRARY], result_opt=[ALL_V_E], alias=[c])\n"
+                    + "    GraphLogicalSource(tableConfig=[{isAll=true, tables=[software,"
+                    + " person]}], alias=[a], opt=[VERTEX])\n"
+                    + "], matchOpt=[INNER])",
+                node.explain().trim());
+    }
 }