Repository: alibaba/GraphScope (language: python)

Issue:
[GIE Compiler] Support unfold() Step in Gremlin Queries 
**Is your feature request related to a problem? Please describe.**
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]

support gremlin step `unfold`:

```
gremlin> g.V().fold().unfold()
==>v[1]
==>v[2]
==>v[3]
==>v[4]
==>v[5]
==>v[6]
```


[BUG] run the environment preparing script will delete exist docker
**Describe the bug**
# run the environment preparing script.
./scripts/prepare_env.sh
During running this script will delete my existing docker instance, without warning or checking if docker exists. 
Really confused.



Discussion / hints:

Hi @panjunjun, could you please provide us more detail about your issue? e.g., OS version, docker verison.

Fix patch:
diff --git a/docs/interactive_engine/tinkerpop/supported_gremlin_steps.md b/docs/interactive_engine/tinkerpop/supported_gremlin_steps.md
index 17fe1a18fc21..27ab04e5e416 100644
--- a/docs/interactive_engine/tinkerpop/supported_gremlin_steps.md
+++ b/docs/interactive_engine/tinkerpop/supported_gremlin_steps.md
@@ -12,6 +12,7 @@
    9. [Match](#match)
    10. [Subgraph](#subgraph)
    11. [Identity](#identity)
+   12. [Unfold](#unfold)
 3. [Syntactic Sugars](#syntactic-sugars)
    1. [PathExpand](#pathexpand)
    2. [Expression](#expression)
@@ -554,7 +555,6 @@ graphName - the name of the side-effect key that will hold the subgraph.
 g.E().subgraph("all")
 g.V().has('name', "marko").outE("knows").subgraph("partial")
 ```
-
 ### Identity
 #### [identity()](https://tinkerpop.apache.org/docs/current/reference/#identity-step)
 The identity()-step maps the current object to itself.
@@ -564,7 +564,14 @@ g.V().identity().values("id")
 g.V().hasLabel("person").as("a").identity().values("id")
 g.V().has("name", "marko").union(identity(), out()).values("id")
 ```
-
+### Unfold
+#### [unfold()](https://tinkerpop.apache.org/docs/current/reference/#unfold-step)
+The unfold()-step unrolls an iterator, iterable or map into a linear form.
+```bash
+g.V().fold().unfold().values("id")
+g.V().fold().as("a").unfold().values("id")
+g.V().has("name", "marko").fold().as("a").select("a").unfold().values("id")
+```
 ## Syntactic Sugars
 The following steps are extended to denote more complex situations.
 ### PathExpand
diff --git a/google-java-format-1.13.0-all-deps.jar b/google-java-format-1.13.0-all-deps.jar
new file mode 100644
index 000000000000..b490b648a789
Binary files /dev/null and b/google-java-format-1.13.0-all-deps.jar differ
diff --git a/interactive_engine/compiler/src/main/antlr4/GremlinGS.g4 b/interactive_engine/compiler/src/main/antlr4/GremlinGS.g4
index 5892fb1f24e0..96fc9b3b3404 100644
--- a/interactive_engine/compiler/src/main/antlr4/GremlinGS.g4
+++ b/interactive_engine/compiler/src/main/antlr4/GremlinGS.g4
@@ -84,6 +84,7 @@ traversalMethod
     | traversalMethod_match // match()
     | traversalMethod_subgraph // subgraph()
     | traversalMethod_bothV // bothV()
+    | traversalMethod_unfold // unfold()
     | traversalMethod_aggregate_func
     | traversalMethod_hasNot // hasNot()
     | traversalMethod_coin  // coin()
@@ -455,11 +456,11 @@ traversalMethod_union
     : 'union' LPAREN nestedTraversalExpr RPAREN
     ;
 
-// coin(0.5)
 traversalMethod_identity
     : 'identity' LPAREN RPAREN
     ;
 
+// coin(0.5)
 traversalMethod_coin
 	: 'coin' LPAREN floatLiteral RPAREN
 	;
@@ -500,6 +501,10 @@ traversalMethod_bothV
 	: 'bothV' LPAREN RPAREN
 	;
 
+traversalMethod_unfold
+    : 'unfold' LPAREN RPAREN
+    ;
+
 traversalMethod_id
 	: 'id' LPAREN RPAREN
 	;
diff --git a/interactive_engine/compiler/src/main/java/com/alibaba/graphscope/common/IrPlan.java b/interactive_engine/compiler/src/main/java/com/alibaba/graphscope/common/IrPlan.java
index b184998edb34..1cd083091062 100644
--- a/interactive_engine/compiler/src/main/java/com/alibaba/graphscope/common/IrPlan.java
+++ b/interactive_engine/compiler/src/main/java/com/alibaba/graphscope/common/IrPlan.java
@@ -556,6 +556,25 @@ public Pointer apply(InterOpBase baseOp) {
                 return ptrUnion;
             }
         },
+        UNFOLD_OP {
+            public Pointer apply(InterOpBase baseOp) {
+                UnfoldOp unfoldOp = (UnfoldOp) baseOp;
+
+                Pointer ptrUnfold = irCoreLib.initUnfoldOperator();
+                FfiAlias.ByValue tag = (FfiAlias.ByValue) unfoldOp.getUnfoldTag().get().applyArg();
+
+                Optional<OpArg> aliasOpt = baseOp.getAlias();
+
+                if (aliasOpt.isPresent()) {
+                    FfiAlias.ByValue alias = (FfiAlias.ByValue) aliasOpt.get().applyArg();
+                    irCoreLib.setUnfoldPair(ptrUnfold, tag.alias, alias.alias);
+                } else {
+                    irCoreLib.setUnfoldPair(ptrUnfold, tag.alias, ArgUtils.asNoneNameOrId());
+                }
+
+                return ptrUnfold;
+            }
+        },
         MATCH_OP {
             public Pointer apply(InterOpBase baseOp) {
                 MatchOp matchOp = (MatchOp) baseOp;
@@ -850,6 +869,9 @@ private IntByReference appendInterOp(int parentId, InterOpBase base)
         } else if (ClassUtils.equalClass(base, MatchOp.class)) {
             Pointer ptrMatch = TransformFactory.MATCH_OP.apply(base);
             error = irCoreLib.appendPatternOperator(ptrPlan, ptrMatch, oprId.getValue(), oprId);
+        } else if (ClassUtils.equalClass(base, UnfoldOp.class)) {
+            Pointer ptrUnfold = TransformFactory.UNFOLD_OP.apply(base);
+            error = irCoreLib.appendUnfoldOperator(ptrPlan, ptrUnfold, oprId.getValue(), oprId);
         } else if (ClassUtils.equalClass(base, AsNoneOp.class)) {
             Pointer ptrAs = TransformFactory.AS_NONE_OP.apply(base);
             error = irCoreLib.appendAsOperator(ptrPlan, ptrAs, oprId.getValue(), oprId);
diff --git a/interactive_engine/compiler/src/main/java/com/alibaba/graphscope/common/intermediate/InterOpCollection.java b/interactive_engine/compiler/src/main/java/com/alibaba/graphscope/common/intermediate/InterOpCollection.java
index ce04fd29c3a7..8b3667a001e3 100644
--- a/interactive_engine/compiler/src/main/java/com/alibaba/graphscope/common/intermediate/InterOpCollection.java
+++ b/interactive_engine/compiler/src/main/java/com/alibaba/graphscope/common/intermediate/InterOpCollection.java
@@ -25,6 +25,7 @@
 import com.alibaba.graphscope.common.intermediate.process.SubGraphProjectProcessor;
 import com.alibaba.graphscope.common.intermediate.strategy.InterOpStrategy;
 import com.alibaba.graphscope.common.intermediate.strategy.TopKStrategy;
+import com.alibaba.graphscope.common.intermediate.strategy.UnfoldFusionStrategy;
 
 import org.apache.commons.collections.list.UnmodifiableList;
 
@@ -36,7 +37,8 @@
 // collection of intermediate operators
 public class InterOpCollection {
     private List<InterOpBase> opCollection;
-    private static List<InterOpStrategy> strategies = Arrays.asList(TopKStrategy.INSTANCE);
+    private static List<InterOpStrategy> strategies =
+            Arrays.asList(TopKStrategy.INSTANCE, UnfoldFusionStrategy.INSTANCE);
     // order matters, process SubGraphProject before the SinkOutput
     private static List<InterOpProcessor> processors =
             Arrays.asList(SubGraphProjectProcessor.INSTANCE, SinkOutputProcessor.INSTANCE);
diff --git a/interactive_engine/compiler/src/main/java/com/alibaba/graphscope/common/intermediate/operator/UnfoldOp.java b/interactive_engine/compiler/src/main/java/com/alibaba/graphscope/common/intermediate/operator/UnfoldOp.java
new file mode 100644
index 000000000000..c5b926ff3692
--- /dev/null
+++ b/interactive_engine/compiler/src/main/java/com/alibaba/graphscope/common/intermediate/operator/UnfoldOp.java
@@ -0,0 +1,46 @@
+/*
+ * Copyright 2020 Alibaba Group Holding Limited.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.alibaba.graphscope.common.intermediate.operator;
+
+import java.util.Optional;
+
+public class UnfoldOp extends InterOpBase {
+    private Optional<OpArg> tag;
+    private Optional<OpArg> alias;
+
+    public UnfoldOp() {
+        super();
+        this.tag = Optional.empty();
+        this.alias = Optional.empty();
+    }
+
+    public Optional<OpArg> getUnfoldTag() {
+        return tag;
+    }
+
+    public Optional<OpArg> getUnfoldAlias() {
+        return alias;
+    }
+
+    public void setUnfoldTag(OpArg tag) {
+        this.tag = Optional.of(tag);
+    }
+
+    public void setUnfoldAlias(OpArg alias) {
+        this.alias = Optional.of(alias);
+    }
+}
diff --git a/interactive_engine/compiler/src/main/java/com/alibaba/graphscope/common/intermediate/strategy/UnfoldFusionStrategy.java b/interactive_engine/compiler/src/main/java/com/alibaba/graphscope/common/intermediate/strategy/UnfoldFusionStrategy.java
new file mode 100644
index 000000000000..8632e33f2eca
--- /dev/null
+++ b/interactive_engine/compiler/src/main/java/com/alibaba/graphscope/common/intermediate/strategy/UnfoldFusionStrategy.java
@@ -0,0 +1,87 @@
+/*
+ * Copyright 2020 Alibaba Group Holding Limited.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.alibaba.graphscope.common.intermediate.strategy;
+
+import com.alibaba.graphscope.common.intermediate.ArgUtils;
+import com.alibaba.graphscope.common.intermediate.InterOpCollection;
+import com.alibaba.graphscope.common.intermediate.operator.InterOpBase;
+import com.alibaba.graphscope.common.intermediate.operator.OpArg;
+import com.alibaba.graphscope.common.intermediate.operator.ProjectOp;
+import com.alibaba.graphscope.common.intermediate.operator.UnfoldOp;
+import com.alibaba.graphscope.common.jna.type.*;
+
+import org.javatuples.Pair;
+
+import java.util.List;
+
+// fuse select as with the following unfold
+public class UnfoldFusionStrategy implements InterOpStrategy {
+    public static UnfoldFusionStrategy INSTANCE = new UnfoldFusionStrategy();
+
+    private UnfoldFusionStrategy() {}
+
+    @Override
+    public void apply(InterOpCollection opCollection) {
+        List<InterOpBase> original = opCollection.unmodifiableCollection();
+        int off = 0;
+        for (int i = 0; i < original.size(); ++i) {
+            InterOpBase cur = original.get(i);
+            UnfoldOp next = nextUnfold(original, i);
+            if (cur instanceof ProjectOp && next != null) {
+                ProjectOp projectOp = (ProjectOp) cur;
+                FfiAlias.ByValue alias_name;
+
+                // skip for the following conditions (may be impossible, e.g., select("a",
+                // "b").unfold())
+                // select("a").as("b").unfold()
+                // select("a", "b").as("c").unfold()
+                // select("a", "b").as("c", "d").unfold()
+                // [unsure]? select("a").by("name").unfold()
+                if (projectOp.getAlias().isPresent()) {
+                    alias_name = (FfiAlias.ByValue) projectOp.getAlias().get().getArg();
+                    if (alias_name.alias.name != null) {
+                        continue;
+                    }
+                }
+
+                List<Pair<String, FfiAlias.ByValue>> pairList =
+                        (List<Pair<String, FfiAlias.ByValue>>)
+                                projectOp.getExprWithAlias().get().getArg();
+
+                // select("a", "b").unfold()
+                if (pairList.size() >= 2) {
+                    continue;
+                }
+
+                Pair single = pairList.get(0);
+                String tag_name = (String) single.getValue0();
+                next.setUnfoldTag(new OpArg<>(ArgUtils.asAlias(tag_name.substring(1), true)));
+
+                opCollection.removeInterOp(i - off);
+                off += 1;
+            }
+        }
+    }
+
+    // return UnfoldOp if next is, otherwise null
+    private UnfoldOp nextUnfold(List<InterOpBase> original, int cur) {
+        int next = cur + 1;
+        return (next >= 0 && next < original.size() && original.get(next) instanceof UnfoldOp)
+                ? (UnfoldOp) original.get(next)
+                : null;
+    }
+}
diff --git a/interactive_engine/compiler/src/main/java/com/alibaba/graphscope/common/jna/IrCoreLibrary.java b/interactive_engine/compiler/src/main/java/com/alibaba/graphscope/common/jna/IrCoreLibrary.java
index e669e4f8efa3..656b0bd72650 100644
--- a/interactive_engine/compiler/src/main/java/com/alibaba/graphscope/common/jna/IrCoreLibrary.java
+++ b/interactive_engine/compiler/src/main/java/com/alibaba/graphscope/common/jna/IrCoreLibrary.java
@@ -242,6 +242,14 @@ FfiResult.ByValue appendPatternOperator(
 
     FfiResult.ByValue addPatternMeta(Pointer pattern, FfiPbPointer.ByValue meta);
 
+    Pointer initUnfoldOperator();
+
+    FfiResult.ByValue appendUnfoldOperator(
+            Pointer plan, Pointer unfold, int parent, IntByReference oprIdx);
+
+    FfiResult.ByValue setUnfoldPair(
+            Pointer unfold, FfiNameOrId.ByValue tag, FfiNameOrId.ByValue alias);
+
     FfiResult.ByValue setUnfoldMeta(Pointer unfold, FfiPbPointer.ByValue meta);
 
     Pointer initJoinOperator(FfiJoinKind joinKind);
diff --git a/interactive_engine/compiler/src/main/java/com/alibaba/graphscope/gremlin/InterOpCollectionBuilder.java b/interactive_engine/compiler/src/main/java/com/alibaba/graphscope/gremlin/InterOpCollectionBuilder.java
index e536c1af83b9..0ee8777d5ed3 100644
--- a/interactive_engine/compiler/src/main/java/com/alibaba/graphscope/gremlin/InterOpCollectionBuilder.java
+++ b/interactive_engine/compiler/src/main/java/com/alibaba/graphscope/gremlin/InterOpCollectionBuilder.java
@@ -20,6 +20,7 @@
 import com.alibaba.graphscope.common.intermediate.ArgUtils;
 import com.alibaba.graphscope.common.intermediate.InterOpCollection;
 import com.alibaba.graphscope.common.intermediate.operator.*;
+import com.alibaba.graphscope.common.jna.type.*;
 import com.alibaba.graphscope.gremlin.exception.UnsupportedStepException;
 import com.alibaba.graphscope.gremlin.plugin.step.*;
 import com.alibaba.graphscope.gremlin.plugin.step.GroupCountStep;
@@ -52,6 +53,7 @@ public InterOpCollectionBuilder(Traversal traversal) {
     public InterOpCollection build() throws OpArgIllegalException, UnsupportedStepException {
         InterOpCollection opCollection = new InterOpCollection();
         List<Step> steps = traversal.asAdmin().getSteps();
+
         for (Step step : steps) {
             List<InterOpBase> opList = new ArrayList<>();
             // judge by class type instead of instance
@@ -140,6 +142,8 @@ public InterOpCollection build() throws OpArgIllegalException, UnsupportedStepEx
                 opList.add(StepTransformFactory.IDENTITY_STEP.apply(step));
             } else if (Utils.equalClass(step, ConstantStep.class)) {
                 opList.add(StepTransformFactory.CONSTANT_STEP.apply(step));
+            } else if (Utils.equalClass(step, UnfoldStep.class)) {
+                opList.add(StepTransformFactory.UNFOLD_STEP.apply(step));
             } else if (Utils.equalClass(step, CoinStep.class)) {
                 opList.add(StepTransformFactory.COIN_STEP.apply(step));
             } else if (Utils.equalClass(step, SampleGlobalStep.class)) {
@@ -149,8 +153,10 @@ public InterOpCollection build() throws OpArgIllegalException, UnsupportedStepEx
             } else {
                 throw new UnsupportedStepException(step.getClass(), "unimplemented yet");
             }
+
             for (int i = 0; i < opList.size(); ++i) {
                 InterOpBase op = opList.get(i);
+
                 // last op
                 if (i == opList.size() - 1) {
                     // set alias
@@ -164,6 +170,7 @@ public InterOpCollection build() throws OpArgIllegalException, UnsupportedStepEx
                         op.setAlias(new OpArg(ArgUtils.asAlias(label, true)));
                     }
                 }
+
                 opCollection.appendInterOp(op);
             }
         }
diff --git a/interactive_engine/compiler/src/main/java/com/alibaba/graphscope/gremlin/antlr4/TraversalMethodVisitor.java b/interactive_engine/compiler/src/main/java/com/alibaba/graphscope/gremlin/antlr4/TraversalMethodVisitor.java
index a8b99c200b65..5c9527aeeeb4 100644
--- a/interactive_engine/compiler/src/main/java/com/alibaba/graphscope/gremlin/antlr4/TraversalMethodVisitor.java
+++ b/interactive_engine/compiler/src/main/java/com/alibaba/graphscope/gremlin/antlr4/TraversalMethodVisitor.java
@@ -747,6 +747,12 @@ public Traversal visitTraversalMethod_bothV(
         return graphTraversal.bothV();
     }
 
+    @Override
+    public Traversal visitTraversalMethod_unfold(
+            final GremlinGSParser.TraversalMethod_unfoldContext ctx) {
+        return graphTraversal.unfold();
+    }
+
     @Override
     public Traversal visitTraversalMethod_coin(
             final GremlinGSParser.TraversalMethod_coinContext ctx) {
diff --git a/interactive_engine/compiler/src/main/java/com/alibaba/graphscope/gremlin/integration/suite/standard/IrGremlinQueryTest.java b/interactive_engine/compiler/src/main/java/com/alibaba/graphscope/gremlin/integration/suite/standard/IrGremlinQueryTest.java
index 0c26eb00f415..3f7920f41c0f 100644
--- a/interactive_engine/compiler/src/main/java/com/alibaba/graphscope/gremlin/integration/suite/standard/IrGremlinQueryTest.java
+++ b/interactive_engine/compiler/src/main/java/com/alibaba/graphscope/gremlin/integration/suite/standard/IrGremlinQueryTest.java
@@ -92,6 +92,19 @@ public abstract class IrGremlinQueryTest extends AbstractGremlinProcessTest {
 
     public abstract Traversal<Vertex, Object> get_g_V_has_union_identity_out_values();
 
+    public abstract Traversal<Vertex, Object> get_g_V_fold_unfold_values();
+
+    public abstract Traversal<Vertex, Object> get_g_V_fold_a_unfold_values();
+
+    public abstract Traversal<Vertex, Object> get_g_V_fold_a_unfold_select_a_unfold_values();
+
+    public abstract Traversal<Vertex, Object> get_g_V_has_select_unfold_values();
+
+    public abstract Traversal<Vertex, Object>
+            get_g_V_has_fold_select_as_unfold_select_unfold_values();
+
+    public abstract Traversal<Vertex, Object> get_g_V_has_fold_select_as_unfold_values();
+
     @LoadGraphWith(LoadGraphWith.GraphData.MODERN)
     @Test
     public void g_V_group_by_by_dedup_count_test() {
@@ -428,6 +441,112 @@ public void g_V_haslabel_union_identity_out_values() {
         Assert.assertEquals(4, counter);
     }
 
+    @LoadGraphWith(LoadGraphWith.GraphData.MODERN)
+    @Test
+    public void g_V_fold_unfold_values() {
+        Traversal<Vertex, Object> traversal = this.get_g_V_fold_unfold_values();
+        this.printTraversalForm(traversal);
+        int counter = 0;
+
+        List<String> expected = Arrays.asList("1", "2", "3", "4", "5", "6");
+
+        while (traversal.hasNext()) {
+            Object result = traversal.next();
+            Assert.assertTrue(expected.contains(result.toString()));
+            ++counter;
+        }
+        Assert.assertEquals(6, counter);
+    }
+
+    @LoadGraphWith(LoadGraphWith.GraphData.MODERN)
+    @Test
+    public void g_V_fold_a_unfold_values() {
+        Traversal<Vertex, Object> traversal = this.get_g_V_fold_a_unfold_values();
+        this.printTraversalForm(traversal);
+        int counter = 0;
+
+        List<String> expected = Arrays.asList("1", "2", "3", "4", "5", "6");
+
+        while (traversal.hasNext()) {
+            Object result = traversal.next();
+            Assert.assertTrue(expected.contains(result.toString()));
+            ++counter;
+        }
+        Assert.assertEquals(6, counter);
+    }
+
+    @LoadGraphWith(LoadGraphWith.GraphData.MODERN)
+    @Test
+    public void g_V_fold_a_unfold_select_a_unfold_values() {
+        Traversal<Vertex, Object> traversal = this.get_g_V_fold_a_unfold_select_a_unfold_values();
+        this.printTraversalForm(traversal);
+
+        List<String> expected = Arrays.asList("1", "2", "3", "4", "5", "6");
+        int counter[] = {6, 6, 6, 6, 6, 6};
+
+        while (traversal.hasNext()) {
+            Object result = traversal.next();
+            Assert.assertTrue(expected.contains(result.toString()));
+            int index = Integer.valueOf(result.toString());
+            counter[index - 1] -= 1;
+        }
+        for (int i = 0; i < 6; ++i) {
+            Assert.assertEquals(counter[i], 0);
+        }
+    }
+
+    @LoadGraphWith(LoadGraphWith.GraphData.MODERN)
+    @Test
+    public void g_V_has_select_unfold_values() {
+        Traversal<Vertex, Object> traversal = this.get_g_V_has_select_unfold_values();
+        this.printTraversalForm(traversal);
+        int counter = 0;
+
+        List<String> expected = Arrays.asList("1");
+
+        while (traversal.hasNext()) {
+            Object result = traversal.next();
+            Assert.assertTrue(expected.contains(result.toString()));
+            ++counter;
+        }
+        Assert.assertEquals(1, counter);
+    }
+
+    @LoadGraphWith(LoadGraphWith.GraphData.MODERN)
+    @Test
+    public void g_V_has_fold_select_as_unfold_select_unfold_values() {
+        Traversal<Vertex, Object> traversal =
+                this.get_g_V_has_fold_select_as_unfold_select_unfold_values();
+        this.printTraversalForm(traversal);
+
+        List<String> expected = Arrays.asList("1");
+        int counter = 0;
+
+        while (traversal.hasNext()) {
+            Object result = traversal.next();
+            Assert.assertTrue(expected.contains(result.toString()));
+            ++counter;
+        }
+        Assert.assertEquals(1, counter);
+    }
+
+    @LoadGraphWith(LoadGraphWith.GraphData.MODERN)
+    @Test
+    public void g_V_has_fold_select_as_unfold_values() {
+        Traversal<Vertex, Object> traversal = this.get_g_V_has_fold_select_as_unfold_values();
+        this.printTraversalForm(traversal);
+
+        List<String> expected = Arrays.asList("1");
+        int counter = 0;
+
+        while (traversal.hasNext()) {
+            Object result = traversal.next();
+            Assert.assertTrue(expected.contains(result.toString()));
+            ++counter;
+        }
+        Assert.assertEquals(1, counter);
+    }
+
     public static class Traversals extends IrGremlinQueryTest {
 
         @Override
@@ -575,5 +694,49 @@ public Traversal<Vertex, Object> get_g_V_haslabel_as_identity_values() {
         public Traversal<Vertex, Object> get_g_V_has_union_identity_out_values() {
             return g.V().has("name", "marko").union(identity(), out()).values("id");
         }
+
+        @Override
+        public Traversal<Vertex, Object> get_g_V_fold_unfold_values() {
+            return g.V().fold().unfold().values("id");
+        }
+
+        @Override
+        public Traversal<Vertex, Object> get_g_V_fold_a_unfold_values() {
+            return g.V().fold().as("a").unfold().values("id");
+        }
+
+        @Override
+        public Traversal<Vertex, Object> get_g_V_fold_a_unfold_select_a_unfold_values() {
+            return g.V().fold().as("a").unfold().select("a").unfold().values("id");
+        }
+
+        @Override
+        public Traversal<Vertex, Object> get_g_V_has_select_unfold_values() {
+            return g.V().has("name", "marko").fold().as("a").select("a").unfold().values("id");
+        }
+
+        @Override
+        public Traversal<Vertex, Object> get_g_V_has_fold_select_as_unfold_select_unfold_values() {
+            return g.V().has("name", "marko")
+                    .fold()
+                    .as("a")
+                    .select("a")
+                    .as("b")
+                    .unfold()
+                    .select("b")
+                    .unfold()
+                    .values("id");
+        }
+
+        @Override
+        public Traversal<Vertex, Object> get_g_V_has_fold_select_as_unfold_values() {
+            return g.V().has("name", "marko")
+                    .fold()
+                    .as("a")
+                    .select("a")
+                    .as("b")
+                    .unfold()
+                    .values("id");
+        }
     }
 }
diff --git a/interactive_engine/compiler/src/main/java/com/alibaba/graphscope/gremlin/result/GremlinResultAnalyzer.java b/interactive_engine/compiler/src/main/java/com/alibaba/graphscope/gremlin/result/GremlinResultAnalyzer.java
index d408c680efc7..70343405aad5 100644
--- a/interactive_engine/compiler/src/main/java/com/alibaba/graphscope/gremlin/result/GremlinResultAnalyzer.java
+++ b/interactive_engine/compiler/src/main/java/com/alibaba/graphscope/gremlin/result/GremlinResultAnalyzer.java
@@ -42,7 +42,8 @@ public static GremlinResultParser analyze(Traversal traversal) {
                     || Utils.equalClass(step, EdgeVertexStep.class)
                     || Utils.equalClass(step, EdgeOtherVertexStep.class)
                     || Utils.equalClass(step, PathExpandStep.class)
-                    || Utils.equalClass(step, IdentityStep.class)) {
+                    || Utils.equalClass(step, IdentityStep.class)
+                    || Utils.equalClass(step, UnfoldStep.class)) {
                 parserType = GremlinResultParserFactory.GRAPH_ELEMENT;
             } else if (Utils.equalClass(step, CountGlobalStep.class)
                     || Utils.equalClass(step, SumGlobalStep.class)
diff --git a/interactive_engine/compiler/src/main/java/com/alibaba/graphscope/gremlin/transform/StepTransformFactory.java b/interactive_engine/compiler/src/main/java/com/alibaba/graphscope/gremlin/transform/StepTransformFactory.java
index 096acfea00ad..878b5a2939bb 100644
--- a/interactive_engine/compiler/src/main/java/com/alibaba/graphscope/gremlin/transform/StepTransformFactory.java
+++ b/interactive_engine/compiler/src/main/java/com/alibaba/graphscope/gremlin/transform/StepTransformFactory.java
@@ -211,6 +211,19 @@ public InterOpBase apply(Step step) {
             return op;
         }
     },
+    UNFOLD_STEP {
+        // unfold()
+        @Override
+        public InterOpBase apply(Step step) {
+            UnfoldStep unfoldStep = (UnfoldStep) step;
+            UnfoldOp op = new UnfoldOp();
+
+            // new OpArg<>(ArgUtils.asNameOrId(tagname))
+            op.setUnfoldTag(new OpArg<>(ArgUtils.asNoneAlias()));
+
+            return op;
+        }
+    },
     AGGREGATE_STEP {
         // count/sum/min/max/fold/mean(avg)
         @Override
@@ -229,6 +242,7 @@ public InterOpBase apply(Step step) {
                     && aggAlias.alias.opt == FfiNameIdOpt.Name) {
                 step.removeLabel(aggFn.getAlias().alias.name);
             }
+
             return op;
         }
     },
diff --git a/interactive_engine/executor/ir/core/src/plan/logical.rs b/interactive_engine/executor/ir/core/src/plan/logical.rs
index d35cd0c56a5b..9f57e4bcdf2a 100644
--- a/interactive_engine/executor/ir/core/src/plan/logical.rs
+++ b/interactive_engine/executor/ir/core/src/plan/logical.rs
@@ -1578,6 +1578,26 @@ impl AsLogical for pb::Pattern {
     }
 }
 
+impl AsLogical for pb::Unfold {
+    fn preprocess(&mut self, _meta: &StoreMeta, plan_meta: &mut PlanMeta) -> IrResult<()> {
+        if let Some(tag) = self.tag.as_mut() {
+            let curr_node = plan_meta.get_curr_node();
+            let tag_id = get_or_set_tag_id(tag, plan_meta)?;
+            // plan_meta.set_tag_nodes(tag_id, plan_meta.get_curr_referred_nodes().to_vec());
+            
+            let tag_nodes = plan_meta.get_tag_nodes(tag_id).to_vec();
+            plan_meta.refer_to_nodes(curr_node, tag_nodes.clone());
+        }
+
+        if let Some(alias) = self.alias.as_mut() {
+            let alias_id = get_or_set_tag_id(alias, plan_meta)?;
+            plan_meta.set_tag_nodes(alias_id, vec![plan_meta.get_curr_node()]);
+        }
+
+        Ok(())
+    }
+}
+
 impl AsLogical for pb::logical_plan::Operator {
     fn preprocess(&mut self, meta: &StoreMeta, plan_meta: &mut PlanMeta) -> IrResult<()> {
         use pb::logical_plan::operator::Opr;
@@ -1598,6 +1618,7 @@ impl AsLogical for pb::logical_plan::Operator {
                 Opr::Sink(opr) => opr.preprocess(meta, plan_meta)?,
                 Opr::Apply(opr) => opr.preprocess(meta, plan_meta)?,
                 Opr::Pattern(opr) => opr.preprocess(meta, plan_meta)?,
+                Opr::Unfold(opr) => opr.preprocess(meta, plan_meta)?,
                 Opr::Sample(opr) => opr.preprocess(meta, plan_meta)?,
                 _ => {}
             }


Test patch:
diff --git a/interactive_engine/compiler/src/test/java/com/alibaba/graphscope/gremlin/UnfoldStepTest.java b/interactive_engine/compiler/src/test/java/com/alibaba/graphscope/gremlin/UnfoldStepTest.java
new file mode 100644
index 000000000000..81ad725121d1
--- /dev/null
+++ b/interactive_engine/compiler/src/test/java/com/alibaba/graphscope/gremlin/UnfoldStepTest.java
@@ -0,0 +1,55 @@
+/*
+ * Copyright 2020 Alibaba Group Holding Limited.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.alibaba.graphscope.gremlin;
+
+import com.alibaba.graphscope.common.intermediate.ArgUtils;
+import com.alibaba.graphscope.common.intermediate.operator.UnfoldOp;
+import com.alibaba.graphscope.common.jna.type.*;
+import com.alibaba.graphscope.gremlin.transform.StepTransformFactory;
+
+import org.apache.tinkerpop.gremlin.process.traversal.Step;
+import org.apache.tinkerpop.gremlin.process.traversal.Traversal;
+import org.apache.tinkerpop.gremlin.process.traversal.dsl.graph.GraphTraversalSource;
+import org.apache.tinkerpop.gremlin.structure.Graph;
+import org.apache.tinkerpop.gremlin.tinkergraph.structure.TinkerFactory;
+import org.junit.Assert;
+import org.junit.Test;
+
+public class UnfoldStepTest {
+    private Graph graph = TinkerFactory.createModern();
+    private GraphTraversalSource g = graph.traversal();
+
+    @Test
+    public void g_V() {
+        Traversal traversal = g.V().fold().unfold();
+        Step unfoldStep = traversal.asAdmin().getEndStep();
+        UnfoldOp op = (UnfoldOp) StepTransformFactory.UNFOLD_STEP.apply(unfoldStep);
+        FfiAlias.ByValue arg = (FfiAlias.ByValue) op.getUnfoldTag().get().getArg();
+        Assert.assertEquals("", ArgUtils.tagName(arg.alias));
+        Assert.assertEquals(false, op.getUnfoldAlias().isPresent());
+    }
+
+    @Test
+    public void g_E() {
+        Traversal traversal = g.E().fold().unfold();
+        Step unfoldStep = traversal.asAdmin().getEndStep();
+        UnfoldOp op = (UnfoldOp) StepTransformFactory.UNFOLD_STEP.apply(unfoldStep);
+        FfiAlias.ByValue arg = (FfiAlias.ByValue) op.getUnfoldTag().get().getArg();
+        Assert.assertEquals("", ArgUtils.tagName(arg.alias));
+        Assert.assertEquals(false, op.getUnfoldAlias().isPresent());
+    }
+}