From 2ff0633742b4065264c316b1da3827d6ee324132 Mon Sep 17 00:00:00 2001 From: contrueCT Date: Thu, 16 Jul 2026 16:00:00 +0800 Subject: [PATCH] refactor(server): upgrade TinkerPop to 3.7.6 and Groovy 4 (#142) * fix(server): resolve phase1 compatibility gaps --------- Co-authored-by: imbajin --- .github/workflows/cluster-test-ci.yml | 23 +- .github/workflows/commons-ci.yml | 4 +- .github/workflows/pd-store-ci.yml | 72 ++- .github/workflows/server-ci.yml | 29 +- README.md | 6 +- .../apache/hugegraph/ct/env/AbstractEnv.java | 70 ++- .../ct/node/AbstractNodeWrapper.java | 31 +- .../hugegraph/unit/util/DateUtilTest.java | 18 +- hugegraph-commons/pom.xml | 4 +- .../hugegraph/api/cypher/CypherClient.java | 102 ++- .../opencypher/CypherOpProcessor.java | 12 +- .../hugegraph/backend/query/Condition.java | 9 +- .../hugegraph/io/HugeGraphSONModule.java | 65 +- .../hugegraph/structure/HugeFeatures.java | 4 +- .../traversal/optimize/ConditionP.java | 13 +- .../traversal/optimize/HugeCountStep.java | 10 +- .../traversal/optimize/HugeCountStrategy.java | 8 +- .../traversal/optimize/TraversalUtil.java | 56 +- .../apache/hugegraph/version/CoreVersion.java | 2 +- .../static/conf/gremlin-driver-settings.yaml | 8 +- .../assembly/static/conf/gremlin-server.yaml | 31 +- .../assembly/static/conf/remote-objects.yaml | 8 +- .../src/assembly/static/conf/remote.yaml | 8 +- .../src/assembly/travis/ci-service-utils.sh | 180 ++++++ .../travis/conf-raft1/gremlin-server.yaml | 26 +- .../travis/conf-raft2/gremlin-server.yaml | 26 +- .../travis/conf-raft3/gremlin-server.yaml | 26 +- .../src/assembly/travis/start-pd.sh | 6 +- .../src/assembly/travis/start-store.sh | 6 +- hugegraph-server/hugegraph-test/pom.xml | 6 - .../apache/hugegraph/api/CypherApiTest.java | 149 ++++- .../api/cypher/CypherClientTest.java | 129 ++++ .../hugegraph/core/CountStrategyCoreTest.java | 45 ++ .../apache/hugegraph/core/VertexCoreTest.java | 18 + .../tinkerpop/ProcessBasicSuite.java | 23 +- .../tinkerpop/StructureBasicSuite.java | 5 +- .../apache/hugegraph/tinkerpop/TestGraph.java | 26 +- .../tinkerpop/TestGraphProvider.java | 163 ++++- .../optimize/TraversalUtilOptimizeTest.java | 39 ++ .../apache/hugegraph/unit/UnitTestSuite.java | 12 + .../GremlinConfigCompatibilityTest.java | 582 ++++++++++++++++++ .../hugegraph/unit/core/ConditionTest.java | 65 ++ .../GroovyScriptEngineCompatibilityTest.java | 49 ++ .../hugegraph/unit/core/HugeFeaturesTest.java | 37 ++ .../serializer/HugeGraphSONModuleTest.java | 148 +++++ .../src/main/resources/fast-methods.filter | 83 ++- .../src/main/resources/methods.filter | 78 ++- hugegraph-server/pom.xml | 8 +- hugegraph-store/hg-store-core/pom.xml | 8 +- hugegraph-struct/pom.xml | 11 +- .../org/apache/hugegraph/query/Condition.java | 9 +- pom.xml | 14 + 52 files changed, 2380 insertions(+), 190 deletions(-) create mode 100644 hugegraph-server/hugegraph-dist/src/assembly/travis/ci-service-utils.sh create mode 100644 hugegraph-server/hugegraph-test/src/main/java/org/apache/hugegraph/api/cypher/CypherClientTest.java create mode 100644 hugegraph-server/hugegraph-test/src/main/java/org/apache/hugegraph/unit/config/GremlinConfigCompatibilityTest.java create mode 100644 hugegraph-server/hugegraph-test/src/main/java/org/apache/hugegraph/unit/core/GroovyScriptEngineCompatibilityTest.java create mode 100644 hugegraph-server/hugegraph-test/src/main/java/org/apache/hugegraph/unit/core/HugeFeaturesTest.java create mode 100644 hugegraph-server/hugegraph-test/src/main/java/org/apache/hugegraph/unit/serializer/HugeGraphSONModuleTest.java diff --git a/.github/workflows/cluster-test-ci.yml b/.github/workflows/cluster-test-ci.yml index 3ef269e878..8e9f4d870f 100644 --- a/.github/workflows/cluster-test-ci.yml +++ b/.github/workflows/cluster-test-ci.yml @@ -45,8 +45,27 @@ jobs: - name: Run simple cluster test run: | - mvn test -pl hugegraph-cluster-test/hugegraph-clustertest-test -am -P simple-cluster-test + timeout 45m mvn test -pl hugegraph-cluster-test/hugegraph-clustertest-test \ + -am -P simple-cluster-test - name: Run multi cluster test run: | - mvn test -pl hugegraph-cluster-test/hugegraph-clustertest-test -am -P multi-cluster-test + timeout 45m mvn test -pl hugegraph-cluster-test/hugegraph-clustertest-test \ + -am -P multi-cluster-test + + - name: Show cluster diagnostics on failure + if: failure() + run: | + echo "[ci] java processes:" + ps -ef | grep -E "HugeGraph|hg-|java" | grep -v grep || true + echo "[ci] listening tcp ports:" + (ss -ltnp || netstat -ltnp || true) 2>&1 + find hugegraph-cluster-test -path '*/logs/*' -type f | sort | while read -r log; do + echo "--- tail -n 200 $log ---" + tail -n 200 "$log" || true + done + find . -path '*/surefire-reports/*' -type f \ + \( -name '*.txt' -o -name '*.xml' \) | sort | while read -r report; do + echo "--- tail -n 120 $report ---" + tail -n 120 "$report" || true + done diff --git a/.github/workflows/commons-ci.yml b/.github/workflows/commons-ci.yml index 5311ebeee0..5ba4c70f47 100644 --- a/.github/workflows/commons-ci.yml +++ b/.github/workflows/commons-ci.yml @@ -5,8 +5,8 @@ on: push: branches: - master - - /^release-.*$/ - - /^test-.*$/ + - 'release-*' + - 'test-*' pull_request: jobs: diff --git a/.github/workflows/pd-store-ci.yml b/.github/workflows/pd-store-ci.yml index 62006794c7..a12cd7a2a1 100644 --- a/.github/workflows/pd-store-ci.yml +++ b/.github/workflows/pd-store-ci.yml @@ -143,6 +143,23 @@ jobs: run: | mvn test -pl hugegraph-pd/hg-pd-test -am -P pd-rest-test + - name: Show PD diagnostics on failure + if: failure() + run: | + VERSION=$(grep -E '^VersionInBash=' \ + hugegraph-commons/hugegraph-common/src/main/resources/version.properties | cut -d'=' -f2-) + [[ "$VERSION" =~ ^[0-9A-Za-z._-]+$ ]] || { + echo "Invalid VersionInBash: $VERSION" + exit 1 + } + PD_DIR=hugegraph-pd/apache-hugegraph-pd-$VERSION + bash $TRAVIS_DIR/ci-service-utils.sh dump "$PD_DIR" HugeGraphPD || true + find . -path '*/surefire-reports/*' -type f \ + \( -name '*.txt' -o -name '*.xml' \) | sort | while read -r report; do + echo "--- tail -n 120 $report ---" + tail -n 120 "$report" || true + done + - name: Upload coverage to Codecov uses: codecov/codecov-action@v3.0.0 with: @@ -246,6 +263,25 @@ jobs: run: | mvn test -pl hugegraph-store/hg-store-test -am -P store-raftcore-test + - name: Show Store diagnostics on failure + if: failure() + run: | + VERSION=$(grep -E '^VersionInBash=' \ + hugegraph-commons/hugegraph-common/src/main/resources/version.properties | cut -d'=' -f2-) + [[ "$VERSION" =~ ^[0-9A-Za-z._-]+$ ]] || { + echo "Invalid VersionInBash: $VERSION" + exit 1 + } + PD_DIR=hugegraph-pd/apache-hugegraph-pd-$VERSION + STORE_DIR=hugegraph-store/apache-hugegraph-store-$VERSION + bash $TRAVIS_DIR/ci-service-utils.sh dump "$PD_DIR" HugeGraphPD || true + bash $TRAVIS_DIR/ci-service-utils.sh dump "$STORE_DIR" HugeGraphStore || true + find . -path '*/surefire-reports/*' -type f \ + \( -name '*.txt' -o -name '*.xml' \) | sort | while read -r report; do + echo "--- tail -n 120 $report ---" + tail -n 120 "$report" || true + done + - name: Upload coverage to Codecov uses: codecov/codecov-action@v3.0.0 with: @@ -312,10 +348,38 @@ jobs: run: | $TRAVIS_DIR/run-api-test-for-raft.sh $BACKEND $REPORT_DIR - - name: Run TinkerPop test - if: ${{ env.RELEASE_BRANCH == 'true' }} - run: | - $TRAVIS_DIR/run-tinkerpop-test.sh $BACKEND tinkerpop + # HStore remains covered by unit/core/API here; TinkerPop compliance is + # exercised in Server CI on memory and rocksdb. + - name: Run TinkerPop structure test + if: ${{ env.RELEASE_BRANCH == 'true' && env.BACKEND != 'hstore' }} + timeout-minutes: 60 + run: | + $TRAVIS_DIR/run-tinkerpop-test.sh $BACKEND structure + + - name: Run TinkerPop process test + if: ${{ env.RELEASE_BRANCH == 'true' && env.BACKEND != 'hstore' }} + timeout-minutes: 60 + run: | + $TRAVIS_DIR/run-tinkerpop-test.sh $BACKEND process + + - name: Show HStore diagnostics on failure + if: failure() + run: | + VERSION=$(grep -E '^VersionInBash=' \ + hugegraph-commons/hugegraph-common/src/main/resources/version.properties | cut -d'=' -f2-) + [[ "$VERSION" =~ ^[0-9A-Za-z._-]+$ ]] || { + echo "Invalid VersionInBash: $VERSION" + exit 1 + } + PD_DIR=hugegraph-pd/apache-hugegraph-pd-$VERSION + STORE_DIR=hugegraph-store/apache-hugegraph-store-$VERSION + bash $TRAVIS_DIR/ci-service-utils.sh dump "$PD_DIR" HugeGraphPD || true + bash $TRAVIS_DIR/ci-service-utils.sh dump "$STORE_DIR" HugeGraphStore || true + find . -path '*/surefire-reports/*' -type f \ + \( -name '*.txt' -o -name '*.xml' \) | sort | while read -r report; do + echo "--- tail -n 120 $report ---" + tail -n 120 "$report" || true + done - name: Upload coverage to Codecov uses: codecov/codecov-action@v3.0.0 diff --git a/.github/workflows/server-ci.yml b/.github/workflows/server-ci.yml index 586e36da49..68177e397c 100644 --- a/.github/workflows/server-ci.yml +++ b/.github/workflows/server-ci.yml @@ -21,8 +21,14 @@ jobs: HEAD_BRANCH_NAME: ${{ github.head_ref }} BASE_BRANCH_NAME: ${{ github.base_ref }} TARGET_BRANCH_NAME: ${{ github.base_ref != '' && github.base_ref || github.ref_name }} - RELEASE_BRANCH: ${{ startsWith(github.ref_name, 'release-') || startsWith(github.ref_name, 'test-') }} - RAFT_MODE: ${{ startsWith(github.head_ref, 'test') || startsWith(github.head_ref, 'raft') }} + RELEASE_BRANCH: >- + ${{ + startsWith(github.ref_name, 'release-') || + startsWith(github.ref_name, 'test-') || + startsWith(github.head_ref, 'release-') || + startsWith(github.head_ref, 'test-') + }} + RAFT_MODE: ${{ startsWith(github.ref_name, 'raft-') || startsWith(github.head_ref, 'raft-') }} strategy: fail-fast: false @@ -115,14 +121,25 @@ jobs: # TODO: disable raft test in normal PR due to the always timeout problem - name: Run raft test if: ${{ env.RAFT_MODE == 'true' && env.BACKEND == 'rocksdb' }} + timeout-minutes: 45 run: | $TRAVIS_DIR/run-api-test-for-raft.sh $BACKEND $REPORT_DIR - - name: Run TinkerPop test - if: ${{ env.RELEASE_BRANCH == 'true' }} + # TinkerPop compliance is covered by memory and rocksdb in CI. + # HBase still runs compile/unit/core/API because its full suite exceeds the CI budget. + - name: Run TinkerPop structure test + if: ${{ env.RELEASE_BRANCH == 'true' && env.BACKEND != 'hbase' }} + timeout-minutes: 60 run: | - echo "[WARNING] Enter Tinkerpop Test, current 'github.ref_name' is ${{ github.ref_name }}" - $TRAVIS_DIR/run-tinkerpop-test.sh $BACKEND tinkerpop + echo "[WARNING] Enter Tinkerpop Structure Test, current 'github.ref_name' is ${{ github.ref_name }}" + $TRAVIS_DIR/run-tinkerpop-test.sh $BACKEND structure + + - name: Run TinkerPop process test + if: ${{ env.RELEASE_BRANCH == 'true' && env.BACKEND != 'hbase' }} + timeout-minutes: 60 + run: | + echo "[WARNING] Enter Tinkerpop Process Test, current 'github.ref_name' is ${{ github.ref_name }}" + $TRAVIS_DIR/run-tinkerpop-test.sh $BACKEND process - name: Upload coverage to Codecov # TODO: update to v5 later diff --git a/README.md b/README.md index f24f6f7bfd..462cbe14d0 100644 --- a/README.md +++ b/README.md @@ -66,7 +66,7 @@ HugeGraph supports both **standalone** and **distributed** deployments: │ HugeGraph Server (:8080) │ │ ┌──────────┐ ┌──────────┐ ┌──────────────────┐ │ │ │ REST API │ │ Gremlin │ │ Cypher Engine │ │ - │ │(Jersey 3)│ │ (TP 3.5) │ │ (OpenCypher) │ │ + │ │(Jersey 3)│ │ (TP 3.7) │ │ (OpenCypher) │ │ │ └────┬─────┘ └────┬─────┘ └────────┬─────────┘ │ │ └─────────────┼─────────────────┘ │ │ ┌────────▼────────┐ │ @@ -128,7 +128,7 @@ flowchart TB subgraph Server["HugeGraph Server :8080"] API[REST API
Jersey 3] - GS[Gremlin Server
TinkerPop 3.5] + GS[Gremlin Server
TinkerPop 3.7] CS[Cypher Engine
OpenCypher] CORE[Graph Engine
hugegraph-core] @@ -294,7 +294,7 @@ curl http://localhost:8080/versions # "versions": { # "version": "v1", # "core": "1.7.0", -# "gremlin": "3.5.1", +# "gremlin": "3.7.6", # "api": "1.7.0" # } # } diff --git a/hugegraph-cluster-test/hugegraph-clustertest-minicluster/src/main/java/org/apache/hugegraph/ct/env/AbstractEnv.java b/hugegraph-cluster-test/hugegraph-clustertest-minicluster/src/main/java/org/apache/hugegraph/ct/env/AbstractEnv.java index 0c24860929..e85a138bfe 100644 --- a/hugegraph-cluster-test/hugegraph-clustertest-minicluster/src/main/java/org/apache/hugegraph/ct/env/AbstractEnv.java +++ b/hugegraph-cluster-test/hugegraph-clustertest-minicluster/src/main/java/org/apache/hugegraph/ct/env/AbstractEnv.java @@ -21,6 +21,7 @@ import java.util.ArrayList; import java.util.List; +import java.util.concurrent.TimeUnit; import org.apache.hugegraph.ct.base.HGTestLogger; import org.apache.hugegraph.ct.config.ClusterConfig; @@ -28,6 +29,7 @@ import org.apache.hugegraph.ct.config.PDConfig; import org.apache.hugegraph.ct.config.ServerConfig; import org.apache.hugegraph.ct.config.StoreConfig; +import org.apache.hugegraph.ct.node.AbstractNodeWrapper; import org.apache.hugegraph.ct.node.PDNodeWrapper; import org.apache.hugegraph.ct.node.ServerNodeWrapper; import org.apache.hugegraph.ct.node.StoreNodeWrapper; @@ -40,6 +42,8 @@ public abstract class AbstractEnv implements BaseEnv { private static final Logger LOG = HGTestLogger.ENV_LOG; + private static final int NODE_START_TIMEOUT_SECONDS = 120; + private static final int NODE_START_POLL_MILLIS = 1000; protected ClusterConfig clusterConfig; protected List pdNodeWrappers; @@ -88,34 +92,54 @@ protected void init(int pdCnt, int storeCnt, int serverCnt) { public void startCluster() { for (PDNodeWrapper pdNodeWrapper : pdNodeWrappers) { - pdNodeWrapper.start(); - while (!pdNodeWrapper.isStarted()) { - try { - Thread.sleep(1000); - } catch (InterruptedException e) { - throw new RuntimeException(e); - } - } + startNode(pdNodeWrapper); } for (StoreNodeWrapper storeNodeWrapper : storeNodeWrappers) { - storeNodeWrapper.start(); - while (!storeNodeWrapper.isStarted()) { - try { - Thread.sleep(1000); - } catch (InterruptedException e) { - throw new RuntimeException(e); - } - } + startNode(storeNodeWrapper); } for (ServerNodeWrapper serverNodeWrapper : serverNodeWrappers) { - serverNodeWrapper.start(); - while (!serverNodeWrapper.isStarted()) { - try { - Thread.sleep(1000); - } catch (InterruptedException e) { - throw new RuntimeException(e); - } + startNode(serverNodeWrapper); + } + } + + private void startNode(AbstractNodeWrapper nodeWrapper) { + System.out.printf("[cluster-test] starting %s in %s%n", + nodeWrapper.getID(), nodeWrapper.getNodePath()); + nodeWrapper.start(); + waitUntilStarted(nodeWrapper); + } + + private static void waitUntilStarted(AbstractNodeWrapper nodeWrapper) { + long deadline = System.nanoTime() + + TimeUnit.SECONDS.toNanos(NODE_START_TIMEOUT_SECONDS); + while (System.nanoTime() < deadline) { + if (nodeWrapper.isStarted()) { + System.out.printf("[cluster-test] %s started%n", + nodeWrapper.getID()); + return; + } + if (!nodeWrapper.isAlive()) { + nodeWrapper.dumpLog(); + throw new AssertionError(String.format( + "%s failed to start, process status: %s", + nodeWrapper.getID(), nodeWrapper.processStatus())); } + sleepBeforeRetry(); + } + + nodeWrapper.dumpLog(); + throw new AssertionError(String.format( + "%s did not start within %s seconds, process status: %s", + nodeWrapper.getID(), NODE_START_TIMEOUT_SECONDS, + nodeWrapper.processStatus())); + } + + private static void sleepBeforeRetry() { + try { + TimeUnit.MILLISECONDS.sleep(NODE_START_POLL_MILLIS); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + throw new AssertionError("Interrupted while waiting node start", e); } } diff --git a/hugegraph-cluster-test/hugegraph-clustertest-minicluster/src/main/java/org/apache/hugegraph/ct/node/AbstractNodeWrapper.java b/hugegraph-cluster-test/hugegraph-clustertest-minicluster/src/main/java/org/apache/hugegraph/ct/node/AbstractNodeWrapper.java index 8236bb1392..97585756a1 100644 --- a/hugegraph-cluster-test/hugegraph-clustertest-minicluster/src/main/java/org/apache/hugegraph/ct/node/AbstractNodeWrapper.java +++ b/hugegraph-cluster-test/hugegraph-clustertest-minicluster/src/main/java/org/apache/hugegraph/ct/node/AbstractNodeWrapper.java @@ -174,7 +174,36 @@ public void stop() { } public boolean isAlive() { - return this.instance.isAlive(); + return this.instance != null && this.instance.isAlive(); + } + + public String processStatus() { + if (this.instance == null) { + return "not started"; + } + if (this.instance.isAlive()) { + return "alive"; + } + return "exited with code " + this.instance.exitValue(); + } + + public void dumpLog() { + Path logPath = Paths.get(getLogPath()); + System.out.println("===== " + getID() + " log: " + logPath + " ====="); + if (!Files.exists(logPath)) { + System.out.println("Log file does not exist"); + return; + } + + try { + List lines = Files.readAllLines(logPath, StandardCharsets.UTF_8); + int start = Math.max(0, lines.size() - 200); + for (int i = start; i < lines.size(); i++) { + System.out.println(lines.get(i)); + } + } catch (IOException e) { + System.out.println("Failed to read log file: " + e.getMessage()); + } } protected ProcessBuilder runCmd(List startCmd, File stdoutFile) throws IOException { diff --git a/hugegraph-commons/hugegraph-common/src/test/java/org/apache/hugegraph/unit/util/DateUtilTest.java b/hugegraph-commons/hugegraph-common/src/test/java/org/apache/hugegraph/unit/util/DateUtilTest.java index 7c51406b36..6c8214e3fe 100644 --- a/hugegraph-commons/hugegraph-common/src/test/java/org/apache/hugegraph/unit/util/DateUtilTest.java +++ b/hugegraph-commons/hugegraph-common/src/test/java/org/apache/hugegraph/unit/util/DateUtilTest.java @@ -22,6 +22,7 @@ import java.util.List; import java.util.concurrent.CountDownLatch; import java.util.concurrent.atomic.AtomicInteger; +import java.util.concurrent.atomic.AtomicReference; import org.junit.Test; @@ -81,9 +82,11 @@ public void testNow() { @Test public void testParseCornerDateValue() throws InterruptedException { final CountDownLatch latch = new CountDownLatch(1); + final Date expected = DateUtil.parse("0", "yyyy"); int threadCount = 10; List threads = new ArrayList<>(threadCount); AtomicInteger errorCount = new AtomicInteger(0); + AtomicReference firstError = new AtomicReference<>(); for (int t = 0; t < threadCount; t++) { Thread thread = new Thread(() -> { try { @@ -92,9 +95,9 @@ public void testParseCornerDateValue() throws InterruptedException { throw new RuntimeException(e); } try { - Assert.assertEquals(new Date(-62167248343000L), - DateUtil.parse("0", "yyyy")); - } catch (Exception e) { + Assert.assertEquals(expected, DateUtil.parse("0", "yyyy")); + } catch (Throwable e) { + firstError.compareAndSet(null, e); errorCount.incrementAndGet(); } }); @@ -109,6 +112,15 @@ public void testParseCornerDateValue() throws InterruptedException { thread.join(); } + Throwable error = firstError.get(); + if (error != null) { + AssertionError assertion = new AssertionError(String.format( + "Expected concurrent parses to match " + + "baseline result, but got %s failures", + errorCount.get())); + assertion.initCause(error); + throw assertion; + } Assert.assertEquals(0, errorCount.get()); } diff --git a/hugegraph-commons/pom.xml b/hugegraph-commons/pom.xml index b9e780bd32..b45a4d7eec 100644 --- a/hugegraph-commons/pom.xml +++ b/hugegraph-commons/pom.xml @@ -98,10 +98,10 @@ 1.8 2.18.0 1.10 - 2.8.0 + 2.10.1 1.9.4 3.2.2 - 3.12.0 + 3.18.0 2.7 1.13 30.0-jre diff --git a/hugegraph-server/hugegraph-api/src/main/java/org/apache/hugegraph/api/cypher/CypherClient.java b/hugegraph-server/hugegraph-api/src/main/java/org/apache/hugegraph/api/cypher/CypherClient.java index 92ae18c54d..85d98a25ce 100644 --- a/hugegraph-server/hugegraph-api/src/main/java/org/apache/hugegraph/api/cypher/CypherClient.java +++ b/hugegraph-server/hugegraph-api/src/main/java/org/apache/hugegraph/api/cypher/CypherClient.java @@ -17,7 +17,10 @@ package org.apache.hugegraph.api.cypher; +import java.lang.reflect.Array; +import java.util.IdentityHashMap; import java.util.Iterator; +import java.util.LinkedHashMap; import java.util.LinkedList; import java.util.List; import java.util.Map; @@ -29,20 +32,23 @@ import javax.annotation.concurrent.ThreadSafe; import org.apache.commons.configuration2.Configuration; +import org.apache.hugegraph.backend.id.Id; import org.apache.hugegraph.util.E; import org.apache.hugegraph.util.Log; import org.apache.tinkerpop.gremlin.driver.Client; import org.apache.tinkerpop.gremlin.driver.Cluster; import org.apache.tinkerpop.gremlin.driver.Result; import org.apache.tinkerpop.gremlin.driver.ResultSet; -import org.apache.tinkerpop.gremlin.driver.Tokens; -import org.apache.tinkerpop.gremlin.driver.message.RequestMessage; +import org.apache.tinkerpop.gremlin.process.traversal.Path; +import org.apache.tinkerpop.gremlin.util.Tokens; +import org.apache.tinkerpop.gremlin.util.message.RequestMessage; import org.slf4j.Logger; @ThreadSafe public final class CypherClient { private static final Logger LOG = Log.logger(CypherClient.class); + private static final int NORMALIZE_MAX_DEPTH = 32; private final Supplier configurationSupplier; private String userName; private String password; @@ -105,12 +111,102 @@ private List doQueryList(Client client, RequestMessage request) while (iter.hasNext()) { Result data = iter.next(); - list.add(data.getObject()); + list.add(normalize(data.getObject())); } return list; } + static Object normalize(Object value) { + return normalize(value, 0, new IdentityHashMap<>()); + } + + private static Object normalize(Object value, int depth, + IdentityHashMap seen) { + if (value == null) { + return null; + } + if (value instanceof Id) { + return ((Id) value).asObject(); + } + boolean composite = value instanceof Map || value instanceof Path || + value instanceof Iterable || + value.getClass().isArray(); + if (!composite) { + return value; + } + if (depth >= NORMALIZE_MAX_DEPTH) { + throw new IllegalArgumentException( + "Exceeded max normalization depth 32"); + } + if (value instanceof Map) { + if (seen.put(value, Boolean.TRUE) != null) { + throw new IllegalArgumentException( + "Detected cyclic Cypher result"); + } + Map normalized = new LinkedHashMap<>(); + try { + for (Map.Entry entry : ((Map) value).entrySet()) { + normalized.put(normalize(entry.getKey(), depth + 1, seen), + normalize(entry.getValue(), depth + 1, seen)); + } + } finally { + seen.remove(value); + } + return normalized; + } + if (value instanceof Path) { + if (seen.put(value, Boolean.TRUE) != null) { + throw new IllegalArgumentException( + "Detected cyclic Cypher result"); + } + Map normalized = new LinkedHashMap<>(); + try { + Path path = (Path) value; + normalized.put("labels", + normalize(path.labels(), depth + 1, seen)); + normalized.put("objects", + normalize(path.objects(), depth + 1, seen)); + } finally { + seen.remove(value); + } + return normalized; + } + if (value instanceof Iterable) { + if (seen.put(value, Boolean.TRUE) != null) { + throw new IllegalArgumentException( + "Detected cyclic Cypher result"); + } + List normalized = new LinkedList<>(); + try { + for (Object item : (Iterable) value) { + normalized.add(normalize(item, depth + 1, seen)); + } + } finally { + seen.remove(value); + } + return normalized; + } + if (value.getClass().isArray()) { + if (seen.put(value, Boolean.TRUE) != null) { + throw new IllegalArgumentException( + "Detected cyclic Cypher result"); + } + List normalized = new LinkedList<>(); + try { + int length = Array.getLength(value); + for (int i = 0; i < length; i++) { + normalized.add(normalize(Array.get(value, i), depth + 1, + seen)); + } + } finally { + seen.remove(value); + } + return normalized; + } + return value; + } + /** * As Sasl does not support a token, which is a coded string to indicate a legal user, * we had to use a trick to fix it. When the token is set, the password will be set to diff --git a/hugegraph-server/hugegraph-api/src/main/java/org/apache/hugegraph/opencypher/CypherOpProcessor.java b/hugegraph-server/hugegraph-api/src/main/java/org/apache/hugegraph/opencypher/CypherOpProcessor.java index dfad9b9594..e3f0a60364 100644 --- a/hugegraph-server/hugegraph-api/src/main/java/org/apache/hugegraph/opencypher/CypherOpProcessor.java +++ b/hugegraph-server/hugegraph-api/src/main/java/org/apache/hugegraph/opencypher/CypherOpProcessor.java @@ -19,7 +19,7 @@ import static java.util.Collections.emptyMap; import static java.util.Collections.singletonList; import static java.util.Optional.empty; -import static org.apache.tinkerpop.gremlin.driver.message.ResponseStatusCode.SERVER_ERROR; +import static org.apache.tinkerpop.gremlin.util.message.ResponseStatusCode.SERVER_ERROR; import static org.opencypher.gremlin.translation.StatementOption.EXPLAIN; import static org.slf4j.LoggerFactory.getLogger; @@ -33,10 +33,6 @@ import java.util.concurrent.FutureTask; import java.util.concurrent.TimeUnit; -import org.apache.tinkerpop.gremlin.driver.Tokens; -import org.apache.tinkerpop.gremlin.driver.message.RequestMessage; -import org.apache.tinkerpop.gremlin.driver.message.ResponseMessage; -import org.apache.tinkerpop.gremlin.driver.message.ResponseStatusCode; import org.apache.tinkerpop.gremlin.process.traversal.P; import org.apache.tinkerpop.gremlin.process.traversal.TraversalSource; import org.apache.tinkerpop.gremlin.process.traversal.dsl.graph.DefaultGraphTraversal; @@ -49,7 +45,11 @@ import org.apache.tinkerpop.gremlin.server.op.AbstractEvalOpProcessor; import org.apache.tinkerpop.gremlin.server.op.OpProcessorException; import org.apache.tinkerpop.gremlin.structure.Graph; +import org.apache.tinkerpop.gremlin.util.Tokens; import org.apache.tinkerpop.gremlin.util.function.ThrowingConsumer; +import org.apache.tinkerpop.gremlin.util.message.RequestMessage; +import org.apache.tinkerpop.gremlin.util.message.ResponseMessage; +import org.apache.tinkerpop.gremlin.util.message.ResponseStatusCode; import org.opencypher.gremlin.translation.CypherAst; import org.opencypher.gremlin.translation.groovy.GroovyPredicate; import org.opencypher.gremlin.translation.ir.TranslationWriter; @@ -66,7 +66,7 @@ /** * Description of the modifications: *

- * 1) Changed the method signature to adopt the gremlin-server 3.5.1. + * 1) Changed the method signature to adopt the gremlin-server Context API. *

  * public Optional> selectOther(RequestMessage requestMessage)
  * -->
diff --git a/hugegraph-server/hugegraph-core/src/main/java/org/apache/hugegraph/backend/query/Condition.java b/hugegraph-server/hugegraph-core/src/main/java/org/apache/hugegraph/backend/query/Condition.java
index 8d9ecd4332..4351c3379b 100644
--- a/hugegraph-server/hugegraph-core/src/main/java/org/apache/hugegraph/backend/query/Condition.java
+++ b/hugegraph-server/hugegraph-core/src/main/java/org/apache/hugegraph/backend/query/Condition.java
@@ -25,7 +25,6 @@
 import java.util.Objects;
 import java.util.Set;
 import java.util.function.BiFunction;
-import java.util.function.BiPredicate;
 
 import org.apache.commons.lang.ArrayUtils;
 import org.apache.hugegraph.backend.id.Id;
@@ -37,6 +36,7 @@
 import org.apache.hugegraph.util.DateUtil;
 import org.apache.hugegraph.util.E;
 import org.apache.hugegraph.util.NumericUtil;
+import org.apache.tinkerpop.gremlin.process.traversal.PBiPredicate;
 
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.ImmutableSet;
@@ -51,7 +51,7 @@ public enum ConditionType {
         NOT
     }
 
-    public enum RelationType implements BiPredicate {
+    public enum RelationType implements PBiPredicate {
 
         EQ("==", RelationType::equals),
 
@@ -165,6 +165,11 @@ public String string() {
             return this.operator;
         }
 
+        @Override
+        public String getPredicateName() {
+            return this.operator;
+        }
+
         /**
          * Determine two values of any type equal
          *
diff --git a/hugegraph-server/hugegraph-core/src/main/java/org/apache/hugegraph/io/HugeGraphSONModule.java b/hugegraph-server/hugegraph-core/src/main/java/org/apache/hugegraph/io/HugeGraphSONModule.java
index ddb7c1a981..7866e076a5 100644
--- a/hugegraph-server/hugegraph-core/src/main/java/org/apache/hugegraph/io/HugeGraphSONModule.java
+++ b/hugegraph-server/hugegraph-core/src/main/java/org/apache/hugegraph/io/HugeGraphSONModule.java
@@ -103,12 +103,14 @@ public class HugeGraphSONModule extends TinkerPopJacksonModule {
         TYPE_DEFINITIONS = new ConcurrentHashMap<>();
 
         TYPE_DEFINITIONS.put(Optional.class, "Optional");
+        TYPE_DEFINITIONS.put(File.class, "File");
         TYPE_DEFINITIONS.put(Date.class, "Date");
         TYPE_DEFINITIONS.put(UUID.class, "UUID");
 
         // HugeGraph id serializer
         TYPE_DEFINITIONS.put(StringId.class, "StringId");
         TYPE_DEFINITIONS.put(LongId.class, "LongId");
+        TYPE_DEFINITIONS.put(UuidId.class, "UuidId");
         TYPE_DEFINITIONS.put(EdgeId.class, "EdgeId");
 
         // HugeGraph schema serializer
@@ -171,6 +173,7 @@ public static void registerCommonSerializers(SimpleModule module) {
         module.addSerializer(Shard.class, new ShardSerializer());
 
         module.addSerializer(File.class, new FileSerializer());
+        module.addDeserializer(File.class, new FileDeserializer());
 
         boolean useTimestamp = false;
         module.addSerializer(Date.class,
@@ -641,8 +644,8 @@ public T deserialize(JsonParser jsonParser,
                 String idValue = ctxt.readValue(jsonParser, String.class);
                 return (T) IdGenerator.of(idValue);
             } else if (clazz.equals(UuidId.class)) {
-                UUID idValue = ctxt.readValue(jsonParser, UUID.class);
-                return (T) IdGenerator.of(idValue);
+                String idValue = ctxt.readValue(jsonParser, String.class);
+                return (T) IdGenerator.of(UUID.fromString(idValue));
             } else {
                 assert clazz.equals(EdgeId.class);
                 String idValue = ctxt.readValue(jsonParser, String.class);
@@ -924,9 +927,65 @@ public FileSerializer() {
         public void serialize(File file, JsonGenerator jsonGenerator,
                               SerializerProvider provider) throws IOException {
             jsonGenerator.writeStartObject();
-            jsonGenerator.writeStringField("file", file.getName());
+            this.writeFields(file, jsonGenerator);
             jsonGenerator.writeEndObject();
         }
+
+        @Override
+        public void serializeWithType(File file,
+                                      JsonGenerator jsonGenerator,
+                                      SerializerProvider provider,
+                                      TypeSerializer typeSer)
+                throws IOException {
+            WritableTypeId typeId = typeSer.typeId(
+                    file, JsonToken.VALUE_EMBEDDED_OBJECT);
+            typeSer.writeTypePrefix(jsonGenerator, typeId);
+            this.serialize(file, jsonGenerator, provider);
+            typeSer.writeTypeSuffix(jsonGenerator, typeId);
+        }
+
+        private void writeFields(File file, JsonGenerator jsonGenerator)
+                throws IOException {
+            jsonGenerator.writeStringField("file", file.getName());
+        }
+    }
+
+    private static class FileDeserializer extends StdDeserializer {
+
+        public FileDeserializer() {
+            super(File.class);
+        }
+
+        @Override
+        public File deserialize(JsonParser jsonParser,
+                                DeserializationContext ctxt)
+                throws IOException {
+            JsonToken token = jsonParser.currentToken();
+            if (token == null) {
+                token = jsonParser.nextToken();
+            }
+            if (token == JsonToken.VALUE_STRING) {
+                return new File(jsonParser.getValueAsString());
+            }
+            if (token == JsonToken.START_OBJECT) {
+                String file = null;
+                while (jsonParser.nextToken() != JsonToken.END_OBJECT) {
+                    String field = jsonParser.currentName();
+                    jsonParser.nextToken();
+                    if ("file".equals(field)) {
+                        file = jsonParser.getValueAsString();
+                    } else {
+                        jsonParser.skipChildren();
+                    }
+                }
+                if (file == null) {
+                    return (File) ctxt.handleUnexpectedToken(File.class,
+                                                             jsonParser);
+                }
+                return new File(file);
+            }
+            return (File) ctxt.handleUnexpectedToken(File.class, jsonParser);
+        }
     }
 
     private static class BlobSerializer extends StdSerializer {
diff --git a/hugegraph-server/hugegraph-core/src/main/java/org/apache/hugegraph/structure/HugeFeatures.java b/hugegraph-server/hugegraph-core/src/main/java/org/apache/hugegraph/structure/HugeFeatures.java
index f8bdb8c75c..49be0ecbe9 100644
--- a/hugegraph-server/hugegraph-core/src/main/java/org/apache/hugegraph/structure/HugeFeatures.java
+++ b/hugegraph-server/hugegraph-core/src/main/java/org/apache/hugegraph/structure/HugeFeatures.java
@@ -186,7 +186,7 @@ public boolean supportsSerializableValues() {
 
         @Override
         public boolean supportsUniformListValues() {
-            return true;
+            return false;
         }
 
         @Override
@@ -225,7 +225,7 @@ public boolean supportsSerializableValues() {
 
         @Override
         public boolean supportsUniformListValues() {
-            return true;
+            return false;
         }
 
     }
diff --git a/hugegraph-server/hugegraph-core/src/main/java/org/apache/hugegraph/traversal/optimize/ConditionP.java b/hugegraph-server/hugegraph-core/src/main/java/org/apache/hugegraph/traversal/optimize/ConditionP.java
index e41a0df706..2ef93114f1 100644
--- a/hugegraph-server/hugegraph-core/src/main/java/org/apache/hugegraph/traversal/optimize/ConditionP.java
+++ b/hugegraph-server/hugegraph-core/src/main/java/org/apache/hugegraph/traversal/optimize/ConditionP.java
@@ -17,16 +17,23 @@
 
 package org.apache.hugegraph.traversal.optimize;
 
-import java.util.function.BiPredicate;
-
 import org.apache.hugegraph.backend.query.Condition;
 import org.apache.tinkerpop.gremlin.process.traversal.P;
+import org.apache.tinkerpop.gremlin.process.traversal.PBiPredicate;
 
+/**
+ * A HugeGraph-local predicate used by server-side traversal processing.
+ *
+ * This type relies on HugeGraph {@link Condition.RelationType} predicates and
+ * has no registered GraphSON or GraphBinary wire serializer. Remote clients
+ * should use supported TinkerPop predicates or server-side query APIs instead
+ * of sending {@code ConditionP} instances directly.
+ */
 public class ConditionP extends P {
 
     private static final long serialVersionUID = 9094970577400072902L;
 
-    private ConditionP(final BiPredicate predicate,
+    private ConditionP(final PBiPredicate predicate,
                        Object value) {
         super(predicate, value);
     }
diff --git a/hugegraph-server/hugegraph-core/src/main/java/org/apache/hugegraph/traversal/optimize/HugeCountStep.java b/hugegraph-server/hugegraph-core/src/main/java/org/apache/hugegraph/traversal/optimize/HugeCountStep.java
index 403bf5be83..f12a84e40e 100644
--- a/hugegraph-server/hugegraph-core/src/main/java/org/apache/hugegraph/traversal/optimize/HugeCountStep.java
+++ b/hugegraph-server/hugegraph-core/src/main/java/org/apache/hugegraph/traversal/optimize/HugeCountStep.java
@@ -55,12 +55,18 @@ public boolean equals(Object obj) {
 
         HugeCountStep other = (HugeCountStep) obj;
         return Objects.equals(this.originGraphStep,
-                              other.originGraphStep) && this.done == other.done;
+                              other.originGraphStep);
     }
 
     @Override
     public int hashCode() {
-        return Objects.hash(super.hashCode(), this.originGraphStep, this.done);
+        return Objects.hash(super.hashCode(), this.originGraphStep);
+    }
+
+    @Override
+    public void reset() {
+        super.reset();
+        this.done = false;
     }
 
     @Override
diff --git a/hugegraph-server/hugegraph-core/src/main/java/org/apache/hugegraph/traversal/optimize/HugeCountStrategy.java b/hugegraph-server/hugegraph-core/src/main/java/org/apache/hugegraph/traversal/optimize/HugeCountStrategy.java
index ea6de76ac0..e8f9695c88 100644
--- a/hugegraph-server/hugegraph-core/src/main/java/org/apache/hugegraph/traversal/optimize/HugeCountStrategy.java
+++ b/hugegraph-server/hugegraph-core/src/main/java/org/apache/hugegraph/traversal/optimize/HugeCountStrategy.java
@@ -25,11 +25,11 @@
 import java.util.Map;
 import java.util.Objects;
 import java.util.Set;
-import java.util.function.BiPredicate;
 
 import org.apache.tinkerpop.gremlin.process.traversal.Compare;
 import org.apache.tinkerpop.gremlin.process.traversal.Contains;
 import org.apache.tinkerpop.gremlin.process.traversal.P;
+import org.apache.tinkerpop.gremlin.process.traversal.PBiPredicate;
 import org.apache.tinkerpop.gremlin.process.traversal.Step;
 import org.apache.tinkerpop.gremlin.process.traversal.Traversal;
 import org.apache.tinkerpop.gremlin.process.traversal.TraversalStrategy;
@@ -62,8 +62,8 @@ public final class HugeCountStrategy
         extends AbstractTraversalStrategy
         implements TraversalStrategy.OptimizationStrategy {
 
-    private static final Map RANGE_PREDICATES =
-            new HashMap() {{
+    private static final Map RANGE_PREDICATES =
+            new HashMap() {{
                 put(Contains.within, 1L);
                 put(Contains.without, 0L);
             }};
@@ -99,7 +99,7 @@ public void apply(final Traversal.Admin traversal) {
                            ((ConnectiveP) isStepPredicate).getPredicates() :
                            Collections.singletonList(isStepPredicate)) {
                     final Object value = p.getValue();
-                    final BiPredicate predicate = p.getBiPredicate();
+                    final PBiPredicate predicate = p.getBiPredicate();
                     if (value instanceof Number) {
                         final long highRangeOffset =
                                 INCREASED_OFFSET_SCALAR_PREDICATES.contains(predicate) ?
diff --git a/hugegraph-server/hugegraph-core/src/main/java/org/apache/hugegraph/traversal/optimize/TraversalUtil.java b/hugegraph-server/hugegraph-core/src/main/java/org/apache/hugegraph/traversal/optimize/TraversalUtil.java
index e6a56027a1..a59be46a21 100644
--- a/hugegraph-server/hugegraph-core/src/main/java/org/apache/hugegraph/traversal/optimize/TraversalUtil.java
+++ b/hugegraph-server/hugegraph-core/src/main/java/org/apache/hugegraph/traversal/optimize/TraversalUtil.java
@@ -24,7 +24,6 @@
 import java.util.List;
 import java.util.Map;
 import java.util.Optional;
-import java.util.function.BiPredicate;
 import java.util.function.Function;
 import java.util.regex.Matcher;
 import java.util.regex.Pattern;
@@ -59,6 +58,7 @@
 import org.apache.tinkerpop.gremlin.process.traversal.Contains;
 import org.apache.tinkerpop.gremlin.process.traversal.Order;
 import org.apache.tinkerpop.gremlin.process.traversal.P;
+import org.apache.tinkerpop.gremlin.process.traversal.PBiPredicate;
 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.GraphTraversal;
@@ -318,7 +318,7 @@ private static boolean collectPositiveLabelValues(
     private static void addPositiveLabelValues(HasContainer has,
                                                List labels) {
         P predicate = has.getPredicate();
-        BiPredicate bp = predicate.getBiPredicate();
+        PBiPredicate bp = predicate.getBiPredicate();
         if (bp == Compare.eq) {
             labels.add(predicate.getValue());
         } else {
@@ -385,12 +385,12 @@ private static boolean isLabelContainer(HasContainer has) {
     }
 
     static boolean isPositiveLabelContainer(HasContainer has) {
-        if (!isLabelContainer(has)) {
+        if (!isLabelContainer(has) || hasNullLabelValue(has)) {
             return false;
         }
 
         P predicate = has.getPredicate();
-        BiPredicate bp = predicate.getBiPredicate();
+        PBiPredicate bp = predicate.getBiPredicate();
         if (bp == Compare.eq) {
             return true;
         }
@@ -454,7 +454,7 @@ private static boolean hasMatchIndexSensitivePredicate(HasContainer has) {
         List> predicates = new ArrayList<>();
         collectPredicates(predicates, ImmutableList.of(has.getPredicate()));
         for (P pred : predicates) {
-            BiPredicate bp = pred.getBiPredicate();
+            PBiPredicate bp = pred.getBiPredicate();
             if (bp == Compare.neq ||
                 bp == Compare.gt || bp == Compare.gte ||
                 bp == Compare.lt || bp == Compare.lte) {
@@ -541,6 +541,26 @@ private static boolean hasNullPredicate(HasContainer has) {
         return false;
     }
 
+    private static boolean hasNullLabelValue(HasContainer has) {
+        if (!isLabelContainer(has)) {
+            return false;
+        }
+
+        List> predicates = new ArrayList<>();
+        collectPredicates(predicates, ImmutableList.of(has.getPredicate()));
+        for (P pred : predicates) {
+            Object value = pred.getValue();
+            if (value == null) {
+                return true;
+            }
+            if (value instanceof Collection &&
+                ((Collection) value).contains(null)) {
+                return true;
+            }
+        }
+        return false;
+    }
+
     private static boolean hasBooleanIndex(HugeGraph graph,
                                            SchemaLabel schemaLabel,
                                            PropertyKey pkey) {
@@ -591,7 +611,7 @@ private static boolean hasOnlyRangePredicates(HasContainer has) {
         List> predicates = new ArrayList<>();
         collectPredicates(predicates, ImmutableList.of(has.getPredicate()));
         for (P pred : predicates) {
-            BiPredicate bp = pred.getBiPredicate();
+            PBiPredicate bp = pred.getBiPredicate();
             if (bp != Compare.gt && bp != Compare.gte &&
                 bp != Compare.lt && bp != Compare.lte) {
                 return false;
@@ -655,6 +675,9 @@ private static boolean canExtractHasContainers(HugeGraph graph,
 
     static boolean canExtractHasContainer(HugeGraph graph,
                                           HasContainer has) {
+        if (has.getKey() == null || hasNullLabelValue(has)) {
+            return false;
+        }
         if (isSysProp(has.getKey())) {
             return true;
         }
@@ -678,7 +701,7 @@ static boolean canExtractHasContainer(HugeGraph graph,
         List> predicates = new ArrayList<>();
         collectPredicates(predicates, ImmutableList.of(has.getPredicate()));
         for (P pred : predicates) {
-            BiPredicate bp = pred.getBiPredicate();
+            PBiPredicate bp = pred.getBiPredicate();
             if (bp == Compare.gt || bp == Compare.gte ||
                 bp == Compare.lt || bp == Compare.lte) {
                 return false;
@@ -840,7 +863,7 @@ public static void fillConditionQuery(ConditionQuery query,
     public static Condition convHas2Condition(HasContainer has, HugeType type, HugeGraph graph) {
         P p = has.getPredicate();
         E.checkArgument(p != null, "The predicate of has(%s) is null", has);
-        BiPredicate bp = p.getBiPredicate();
+        PBiPredicate bp = p.getBiPredicate();
         Condition condition;
         if (keyForContainsKeyOrValue(has.getKey())) {
             condition = convContains2Relation(graph, has);
@@ -913,7 +936,7 @@ private static Condition convCompare2Relation(HugeGraph graph,
                                                   HugeType type,
                                                   HasContainer has) {
         assert type.isGraph();
-        BiPredicate bp = has.getPredicate().getBiPredicate();
+        PBiPredicate bp = has.getPredicate().getBiPredicate();
         assert bp instanceof Compare;
 
         return isSysProp(has.getKey()) ?
@@ -924,7 +947,7 @@ private static Condition convCompare2Relation(HugeGraph graph,
     private static Condition.Relation convCompare2SyspropRelation(HugeGraph graph,
                                                                   HugeType type,
                                                                   HasContainer has) {
-        BiPredicate bp = has.getPredicate().getBiPredicate();
+        PBiPredicate bp = has.getPredicate().getBiPredicate();
         assert bp instanceof Compare;
 
         HugeKeys key = token2HugeKey(has.getKey());
@@ -952,7 +975,7 @@ private static Condition.Relation convCompare2SyspropRelation(HugeGraph graph,
     private static Condition convCompare2UserpropRelation(HugeGraph graph,
                                                           HugeType type,
                                                           HasContainer has) {
-        BiPredicate bp = has.getPredicate().getBiPredicate();
+        PBiPredicate bp = has.getPredicate().getBiPredicate();
         assert bp instanceof Compare;
 
         String key = has.getKey();
@@ -1012,7 +1035,7 @@ private static Condition convRelationType2Relation(HugeGraph graph,
                                                        HugeType type,
                                                        HasContainer has) {
         assert type.isGraph();
-        BiPredicate bp = has.getPredicate().getBiPredicate();
+        PBiPredicate bp = has.getPredicate().getBiPredicate();
         assert bp instanceof Condition.RelationType;
 
         String key = has.getKey();
@@ -1025,7 +1048,7 @@ private static Condition convRelationType2Relation(HugeGraph graph,
     public static Condition convIn2Relation(HugeGraph graph,
                                             HugeType type,
                                             HasContainer has) {
-        BiPredicate bp = has.getPredicate().getBiPredicate();
+        PBiPredicate bp = has.getPredicate().getBiPredicate();
         assert bp instanceof Contains;
         Collection values = (Collection) has.getValue();
 
@@ -1068,7 +1091,7 @@ public static Condition convIn2Relation(HugeGraph graph,
     public static Condition convContains2Relation(HugeGraph graph,
                                                   HasContainer has) {
         // Convert contains-key or contains-value
-        BiPredicate bp = has.getPredicate().getBiPredicate();
+        PBiPredicate bp = has.getPredicate().getBiPredicate();
         E.checkArgument(bp == Compare.eq, "CONTAINS query with relation " +
                                           "'%s' is not supported", bp);
 
@@ -1097,6 +1120,9 @@ public static HugeKeys string2HugeKey(String key) {
     }
 
     public static HugeKeys token2HugeKey(String key) {
+        if (key == null) {
+            return null;
+        }
         if (key.equals(T.label.getAccessor())) {
             return HugeKeys.LABEL;
         } else if (key.equals(T.id.getAccessor())) {
@@ -1187,7 +1213,7 @@ public static void convHasStep(HugeGraph graph, HasStep step) {
     private static void convPredicateValue(HugeGraph graph,
                                            HasContainer has) {
         // No need to convert if key is sys-prop
-        if (isSysProp(has.getKey())) {
+        if (has.getKey() == null || isSysProp(has.getKey())) {
             return;
         }
         PropertyKey pkey = graph.propertyKey(has.getKey());
diff --git a/hugegraph-server/hugegraph-core/src/main/java/org/apache/hugegraph/version/CoreVersion.java b/hugegraph-server/hugegraph-core/src/main/java/org/apache/hugegraph/version/CoreVersion.java
index 2eff71487a..195bf60376 100644
--- a/hugegraph-server/hugegraph-core/src/main/java/org/apache/hugegraph/version/CoreVersion.java
+++ b/hugegraph-server/hugegraph-core/src/main/java/org/apache/hugegraph/version/CoreVersion.java
@@ -31,7 +31,7 @@ public class CoreVersion {
     /**
      * Update it when the gremlin version changed, search "tinkerpop.version" in pom
      */
-    public static final String GREMLIN_VERSION = "3.5.1";
+    public static final String GREMLIN_VERSION = "3.7.6";
 
     static {
         // Check versions of the dependency packages
diff --git a/hugegraph-server/hugegraph-dist/src/assembly/static/conf/gremlin-driver-settings.yaml b/hugegraph-server/hugegraph-dist/src/assembly/static/conf/gremlin-driver-settings.yaml
index 55f38ab97d..e9a28f0aa9 100644
--- a/hugegraph-server/hugegraph-dist/src/assembly/static/conf/gremlin-driver-settings.yaml
+++ b/hugegraph-server/hugegraph-dist/src/assembly/static/conf/gremlin-driver-settings.yaml
@@ -17,9 +17,15 @@
 hosts: [localhost]
 port: 8182
 serializer: {
-  className: org.apache.tinkerpop.gremlin.driver.ser.GraphSONMessageSerializerV1d0,
+  className: org.apache.tinkerpop.gremlin.util.ser.GraphSONUntypedMessageSerializerV1,
   config: {
     serializeResultToString: false,
     ioRegistries: [org.apache.hugegraph.io.HugeGraphIoRegistry]
   }
 }
+# Typed GraphSON fallback for clients that require @type/@value for
+# File/Id/simple typed values:
+# use org.apache.tinkerpop.gremlin.util.ser.GraphSONMessageSerializerV1 in
+# place of GraphSONUntypedMessageSerializerV1, and keep HugeGraphIoRegistry.
+# Full HugeGraph internal object typed GraphSON for schema/Vertex/Edge/Path
+# remains follow-up scope; keep untyped serializers for normal remote objects.
diff --git a/hugegraph-server/hugegraph-dist/src/assembly/static/conf/gremlin-server.yaml b/hugegraph-server/hugegraph-dist/src/assembly/static/conf/gremlin-server.yaml
index 32135163fd..e61e02a469 100644
--- a/hugegraph-server/hugegraph-dist/src/assembly/static/conf/gremlin-server.yaml
+++ b/hugegraph-server/hugegraph-dist/src/assembly/static/conf/gremlin-server.yaml
@@ -82,30 +82,53 @@ scriptEngines: {
   }
 }
 serializers:
-  - {className: org.apache.tinkerpop.gremlin.driver.ser.GraphBinaryMessageSerializerV1,
+  - {className: org.apache.tinkerpop.gremlin.util.ser.GraphBinaryMessageSerializerV1,
      config: {
        serializeResultToString: false,
        ioRegistries: [org.apache.hugegraph.io.HugeGraphIoRegistry]
      }
   }
-  - {className: org.apache.tinkerpop.gremlin.driver.ser.GraphSONMessageSerializerV1d0,
+  - {className: org.apache.tinkerpop.gremlin.util.ser.GraphSONUntypedMessageSerializerV1,
      config: {
        serializeResultToString: false,
        ioRegistries: [org.apache.hugegraph.io.HugeGraphIoRegistry]
      }
   }
-  - {className: org.apache.tinkerpop.gremlin.driver.ser.GraphSONMessageSerializerV2d0,
+  - {className: org.apache.tinkerpop.gremlin.util.ser.GraphSONUntypedMessageSerializerV2,
      config: {
        serializeResultToString: false,
        ioRegistries: [org.apache.hugegraph.io.HugeGraphIoRegistry]
      }
   }
-  - {className: org.apache.tinkerpop.gremlin.driver.ser.GraphSONMessageSerializerV3d0,
+  - {className: org.apache.tinkerpop.gremlin.util.ser.GraphSONUntypedMessageSerializerV3,
      config: {
        serializeResultToString: false,
        ioRegistries: [org.apache.hugegraph.io.HugeGraphIoRegistry]
      }
   }
+  # Keep untyped GraphSON before typed GraphSON so application/json stays
+  # mapped to the untyped V1 serializer while explicit typed MIME requests work.
+  - {className: org.apache.tinkerpop.gremlin.util.ser.GraphSONMessageSerializerV1,
+     config: {
+       serializeResultToString: false,
+       ioRegistries: [org.apache.hugegraph.io.HugeGraphIoRegistry]
+     }
+  }
+  - {className: org.apache.tinkerpop.gremlin.util.ser.GraphSONMessageSerializerV2,
+     config: {
+       serializeResultToString: false,
+       ioRegistries: [org.apache.hugegraph.io.HugeGraphIoRegistry]
+     }
+  }
+  - {className: org.apache.tinkerpop.gremlin.util.ser.GraphSONMessageSerializerV3,
+     config: {
+       serializeResultToString: false,
+       ioRegistries: [org.apache.hugegraph.io.HugeGraphIoRegistry]
+     }
+  }
+# Typed GraphSON remains fallback scope for File/Id/simple typed values.
+# Full HugeGraph internal object typed GraphSON for schema/Vertex/Edge/Path
+# remains follow-up scope; keep untyped serializers for normal remote objects.
 metrics: {
   consoleReporter: {enabled: false, interval: 180000},
   csvReporter: {enabled: false, interval: 180000, fileName: ./metrics/gremlin-server-metrics.csv},
diff --git a/hugegraph-server/hugegraph-dist/src/assembly/static/conf/remote-objects.yaml b/hugegraph-server/hugegraph-dist/src/assembly/static/conf/remote-objects.yaml
index 39679d8c30..1ab52aa3c8 100644
--- a/hugegraph-server/hugegraph-dist/src/assembly/static/conf/remote-objects.yaml
+++ b/hugegraph-server/hugegraph-dist/src/assembly/static/conf/remote-objects.yaml
@@ -17,7 +17,7 @@
 hosts: [localhost]
 port: 8182
 serializer: {
-  className: org.apache.tinkerpop.gremlin.driver.ser.GraphSONMessageSerializerV1d0,
+  className: org.apache.tinkerpop.gremlin.util.ser.GraphSONUntypedMessageSerializerV1,
   config: {
     serializeResultToString: false,
     # The duplication of HugeGraphIoRegistry is meant to fix a bug in the
@@ -28,3 +28,9 @@ serializer: {
     ]
   }
 }
+# Typed GraphSON fallback for clients that require @type/@value for
+# File/Id/simple typed values:
+# use org.apache.tinkerpop.gremlin.util.ser.GraphSONMessageSerializerV1 in
+# place of GraphSONUntypedMessageSerializerV1, and keep HugeGraphIoRegistry.
+# Full HugeGraph internal object typed GraphSON for schema/Vertex/Edge/Path
+# remains follow-up scope; keep untyped serializers for normal remote objects.
diff --git a/hugegraph-server/hugegraph-dist/src/assembly/static/conf/remote.yaml b/hugegraph-server/hugegraph-dist/src/assembly/static/conf/remote.yaml
index 55f38ab97d..e9a28f0aa9 100644
--- a/hugegraph-server/hugegraph-dist/src/assembly/static/conf/remote.yaml
+++ b/hugegraph-server/hugegraph-dist/src/assembly/static/conf/remote.yaml
@@ -17,9 +17,15 @@
 hosts: [localhost]
 port: 8182
 serializer: {
-  className: org.apache.tinkerpop.gremlin.driver.ser.GraphSONMessageSerializerV1d0,
+  className: org.apache.tinkerpop.gremlin.util.ser.GraphSONUntypedMessageSerializerV1,
   config: {
     serializeResultToString: false,
     ioRegistries: [org.apache.hugegraph.io.HugeGraphIoRegistry]
   }
 }
+# Typed GraphSON fallback for clients that require @type/@value for
+# File/Id/simple typed values:
+# use org.apache.tinkerpop.gremlin.util.ser.GraphSONMessageSerializerV1 in
+# place of GraphSONUntypedMessageSerializerV1, and keep HugeGraphIoRegistry.
+# Full HugeGraph internal object typed GraphSON for schema/Vertex/Edge/Path
+# remains follow-up scope; keep untyped serializers for normal remote objects.
diff --git a/hugegraph-server/hugegraph-dist/src/assembly/travis/ci-service-utils.sh b/hugegraph-server/hugegraph-dist/src/assembly/travis/ci-service-utils.sh
new file mode 100644
index 0000000000..663feda30d
--- /dev/null
+++ b/hugegraph-server/hugegraph-dist/src/assembly/travis/ci-service-utils.sh
@@ -0,0 +1,180 @@
+#!/bin/bash
+#
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements.  See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You 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.
+#
+
+function dump_service_diagnostics() {
+    local service_dir="$1"
+    local service_name="$2"
+    local log_dir="${service_dir}/logs"
+
+    echo "::group::${service_name} diagnostics"
+    echo "[ci] service dir: ${service_dir}"
+    echo "[ci] java processes:"
+    ps -ef | grep -E "HugeGraph|hg-|java" | grep -v grep || true
+    echo "[ci] listening tcp ports:"
+    (ss -ltnp || netstat -ltnp || true) 2>&1
+
+    if [ -d "${log_dir}" ]; then
+        find "${log_dir}" -maxdepth 2 -type f | sort | while read -r log_file; do
+            echo "--- tail -n 200 ${log_file} ---"
+            tail -n 200 "${log_file}" || true
+        done
+    else
+        echo "[ci] log dir not found: ${log_dir}"
+    fi
+    echo "::endgroup::"
+}
+
+function wait_for_tcp_port() {
+    local service_name="$1"
+    local host="$2"
+    local port="$3"
+    local pid_file="$4"
+    local service_dir="$5"
+    local timeout_seconds="${6:-90}"
+
+    echo "[ci] waiting for ${service_name} at ${host}:${port}"
+    for second in $(seq 1 "${timeout_seconds}"); do
+        if bash -c "echo > /dev/tcp/${host}/${port}" >/dev/null 2>&1; then
+            echo "[ci] ${service_name} is listening on ${host}:${port}"
+            return 0
+        fi
+
+        if [ -f "${pid_file}" ]; then
+            local pid
+            pid="$(cat "${pid_file}")"
+            if [ -n "${pid}" ] && ! kill -0 "${pid}" >/dev/null 2>&1; then
+                echo "[ci] ${service_name} process ${pid} exited before readiness"
+                dump_service_diagnostics "${service_dir}" "${service_name}"
+                return 1
+            fi
+        fi
+
+        if [ "$((second % 10))" -eq 0 ]; then
+            echo "[ci] still waiting for ${service_name} (${second}s)"
+        fi
+        sleep 1
+    done
+
+    echo "[ci] timeout waiting for ${service_name} at ${host}:${port}"
+    dump_service_diagnostics "${service_dir}" "${service_name}"
+    return 1
+}
+
+function http_status_is_accepted() {
+    local status="$1"
+    local accepted_statuses="$2"
+
+    case ",${accepted_statuses}," in
+        *",${status},"*)
+            return 0
+            ;;
+        *)
+            return 1
+            ;;
+    esac
+}
+
+function wait_for_http_status() {
+    local service_name="$1"
+    local url="$2"
+    local pid_file="$3"
+    local service_dir="$4"
+    local timeout_seconds="${5:-90}"
+    local accepted_statuses="${6:-200}"
+    local connect_timeout_seconds=2
+    local max_request_seconds=5
+    local started_at="${SECONDS}"
+    local deadline=$((started_at + timeout_seconds))
+    local next_log_at=10
+
+    echo "[ci] waiting for ${service_name} HTTP readiness at ${url}"
+    echo "[ci] accepted HTTP statuses: ${accepted_statuses}"
+    while (( SECONDS < deadline )); do
+        local remaining=$((deadline - SECONDS))
+        local request_timeout="${max_request_seconds}"
+        if (( remaining < request_timeout )); then
+            request_timeout="${remaining}"
+        fi
+        if (( request_timeout < 1 )); then
+            break
+        fi
+
+        local status
+        status="$(curl -s -o /dev/null -w "%{http_code}" \
+                  --connect-timeout "${connect_timeout_seconds}" \
+                  --max-time "${request_timeout}" \
+                  "${url}" 2>/dev/null)" || status="000"
+        if http_status_is_accepted "${status}" "${accepted_statuses}"; then
+            echo "[ci] ${service_name} is HTTP ready at ${url}" \
+                 "(status ${status})"
+            return 0
+        fi
+
+        if [ -f "${pid_file}" ]; then
+            local pid
+            pid="$(cat "${pid_file}")"
+            if [ -n "${pid}" ] && ! kill -0 "${pid}" >/dev/null 2>&1; then
+                echo "[ci] ${service_name} process ${pid} exited before" \
+                     "HTTP readiness"
+                dump_service_diagnostics "${service_dir}" "${service_name}"
+                return 1
+            fi
+        fi
+
+        local elapsed=$((SECONDS - started_at))
+        if (( elapsed >= next_log_at )); then
+            echo "[ci] still waiting for ${service_name} HTTP readiness" \
+                 "(${elapsed}s, last status ${status})"
+            next_log_at=$((next_log_at + 10))
+        fi
+        if (( SECONDS >= deadline )); then
+            break
+        fi
+        sleep 1
+    done
+
+    echo "[ci] timeout waiting for ${service_name} HTTP readiness at ${url}"
+    dump_service_diagnostics "${service_dir}" "${service_name}"
+    return 1
+}
+
+if [[ "${BASH_SOURCE[0]}" == "$0" ]]; then
+    command="$1"
+    shift || true
+    case "${command}" in
+        dump)
+            dump_service_diagnostics "$@"
+            exit $?
+            ;;
+        wait)
+            wait_for_tcp_port "$@"
+            exit $?
+            ;;
+        wait-http)
+            wait_for_http_status "$@"
+            exit $?
+            ;;
+        *)
+            echo "Usage: $0 dump SERVICE_DIR SERVICE_NAME"
+            echo "       $0 wait SERVICE_NAME HOST PORT PID_FILE SERVICE_DIR [TIMEOUT_SECONDS]"
+            echo "       $0 wait-http SERVICE_NAME URL PID_FILE SERVICE_DIR" \
+                 "[TIMEOUT_SECONDS] [ACCEPTED_STATUSES]"
+            exit 2
+            ;;
+    esac
+fi
diff --git a/hugegraph-server/hugegraph-dist/src/assembly/travis/conf-raft1/gremlin-server.yaml b/hugegraph-server/hugegraph-dist/src/assembly/travis/conf-raft1/gremlin-server.yaml
index 7e10eb52b0..7972e96bd6 100644
--- a/hugegraph-server/hugegraph-dist/src/assembly/travis/conf-raft1/gremlin-server.yaml
+++ b/hugegraph-server/hugegraph-dist/src/assembly/travis/conf-raft1/gremlin-server.yaml
@@ -74,25 +74,43 @@ scriptEngines: {
   }
 }
 serializers:
-  - {className: org.apache.tinkerpop.gremlin.driver.ser.GraphBinaryMessageSerializerV1,
+  - {className: org.apache.tinkerpop.gremlin.util.ser.GraphBinaryMessageSerializerV1,
      config: {
        serializeResultToString: false,
        ioRegistries: [org.apache.hugegraph.io.HugeGraphIoRegistry]
      }
   }
-  - {className: org.apache.tinkerpop.gremlin.driver.ser.GraphSONMessageSerializerV1d0,
+  - {className: org.apache.tinkerpop.gremlin.util.ser.GraphSONUntypedMessageSerializerV1,
      config: {
        serializeResultToString: false,
        ioRegistries: [org.apache.hugegraph.io.HugeGraphIoRegistry]
      }
   }
-  - {className: org.apache.tinkerpop.gremlin.driver.ser.GraphSONMessageSerializerV2d0,
+  - {className: org.apache.tinkerpop.gremlin.util.ser.GraphSONUntypedMessageSerializerV2,
      config: {
        serializeResultToString: false,
        ioRegistries: [org.apache.hugegraph.io.HugeGraphIoRegistry]
      }
   }
-  - {className: org.apache.tinkerpop.gremlin.driver.ser.GraphSONMessageSerializerV3d0,
+  - {className: org.apache.tinkerpop.gremlin.util.ser.GraphSONUntypedMessageSerializerV3,
+     config: {
+       serializeResultToString: false,
+       ioRegistries: [org.apache.hugegraph.io.HugeGraphIoRegistry]
+     }
+  }
+  - {className: org.apache.tinkerpop.gremlin.util.ser.GraphSONMessageSerializerV1,
+     config: {
+       serializeResultToString: false,
+       ioRegistries: [org.apache.hugegraph.io.HugeGraphIoRegistry]
+     }
+  }
+  - {className: org.apache.tinkerpop.gremlin.util.ser.GraphSONMessageSerializerV2,
+     config: {
+       serializeResultToString: false,
+       ioRegistries: [org.apache.hugegraph.io.HugeGraphIoRegistry]
+     }
+  }
+  - {className: org.apache.tinkerpop.gremlin.util.ser.GraphSONMessageSerializerV3,
      config: {
        serializeResultToString: false,
        ioRegistries: [org.apache.hugegraph.io.HugeGraphIoRegistry]
diff --git a/hugegraph-server/hugegraph-dist/src/assembly/travis/conf-raft2/gremlin-server.yaml b/hugegraph-server/hugegraph-dist/src/assembly/travis/conf-raft2/gremlin-server.yaml
index 5f097f91bc..019f6a5ccd 100644
--- a/hugegraph-server/hugegraph-dist/src/assembly/travis/conf-raft2/gremlin-server.yaml
+++ b/hugegraph-server/hugegraph-dist/src/assembly/travis/conf-raft2/gremlin-server.yaml
@@ -74,25 +74,43 @@ scriptEngines: {
   }
 }
 serializers:
-  - {className: org.apache.tinkerpop.gremlin.driver.ser.GraphBinaryMessageSerializerV1,
+  - {className: org.apache.tinkerpop.gremlin.util.ser.GraphBinaryMessageSerializerV1,
      config: {
        serializeResultToString: false,
        ioRegistries: [org.apache.hugegraph.io.HugeGraphIoRegistry]
      }
   }
-  - {className: org.apache.tinkerpop.gremlin.driver.ser.GraphSONMessageSerializerV1d0,
+  - {className: org.apache.tinkerpop.gremlin.util.ser.GraphSONUntypedMessageSerializerV1,
      config: {
        serializeResultToString: false,
        ioRegistries: [org.apache.hugegraph.io.HugeGraphIoRegistry]
      }
   }
-  - {className: org.apache.tinkerpop.gremlin.driver.ser.GraphSONMessageSerializerV2d0,
+  - {className: org.apache.tinkerpop.gremlin.util.ser.GraphSONUntypedMessageSerializerV2,
      config: {
        serializeResultToString: false,
        ioRegistries: [org.apache.hugegraph.io.HugeGraphIoRegistry]
      }
   }
-  - {className: org.apache.tinkerpop.gremlin.driver.ser.GraphSONMessageSerializerV3d0,
+  - {className: org.apache.tinkerpop.gremlin.util.ser.GraphSONUntypedMessageSerializerV3,
+     config: {
+       serializeResultToString: false,
+       ioRegistries: [org.apache.hugegraph.io.HugeGraphIoRegistry]
+     }
+  }
+  - {className: org.apache.tinkerpop.gremlin.util.ser.GraphSONMessageSerializerV1,
+     config: {
+       serializeResultToString: false,
+       ioRegistries: [org.apache.hugegraph.io.HugeGraphIoRegistry]
+     }
+  }
+  - {className: org.apache.tinkerpop.gremlin.util.ser.GraphSONMessageSerializerV2,
+     config: {
+       serializeResultToString: false,
+       ioRegistries: [org.apache.hugegraph.io.HugeGraphIoRegistry]
+     }
+  }
+  - {className: org.apache.tinkerpop.gremlin.util.ser.GraphSONMessageSerializerV3,
      config: {
        serializeResultToString: false,
        ioRegistries: [org.apache.hugegraph.io.HugeGraphIoRegistry]
diff --git a/hugegraph-server/hugegraph-dist/src/assembly/travis/conf-raft3/gremlin-server.yaml b/hugegraph-server/hugegraph-dist/src/assembly/travis/conf-raft3/gremlin-server.yaml
index 3f50c64778..96c37f94cd 100644
--- a/hugegraph-server/hugegraph-dist/src/assembly/travis/conf-raft3/gremlin-server.yaml
+++ b/hugegraph-server/hugegraph-dist/src/assembly/travis/conf-raft3/gremlin-server.yaml
@@ -74,25 +74,43 @@ scriptEngines: {
   }
 }
 serializers:
-  - {className: org.apache.tinkerpop.gremlin.driver.ser.GraphBinaryMessageSerializerV1,
+  - {className: org.apache.tinkerpop.gremlin.util.ser.GraphBinaryMessageSerializerV1,
      config: {
        serializeResultToString: false,
        ioRegistries: [org.apache.hugegraph.io.HugeGraphIoRegistry]
      }
   }
-  - {className: org.apache.tinkerpop.gremlin.driver.ser.GraphSONMessageSerializerV1d0,
+  - {className: org.apache.tinkerpop.gremlin.util.ser.GraphSONUntypedMessageSerializerV1,
      config: {
        serializeResultToString: false,
        ioRegistries: [org.apache.hugegraph.io.HugeGraphIoRegistry]
      }
   }
-  - {className: org.apache.tinkerpop.gremlin.driver.ser.GraphSONMessageSerializerV2d0,
+  - {className: org.apache.tinkerpop.gremlin.util.ser.GraphSONUntypedMessageSerializerV2,
      config: {
        serializeResultToString: false,
        ioRegistries: [org.apache.hugegraph.io.HugeGraphIoRegistry]
      }
   }
-  - {className: org.apache.tinkerpop.gremlin.driver.ser.GraphSONMessageSerializerV3d0,
+  - {className: org.apache.tinkerpop.gremlin.util.ser.GraphSONUntypedMessageSerializerV3,
+     config: {
+       serializeResultToString: false,
+       ioRegistries: [org.apache.hugegraph.io.HugeGraphIoRegistry]
+     }
+  }
+  - {className: org.apache.tinkerpop.gremlin.util.ser.GraphSONMessageSerializerV1,
+     config: {
+       serializeResultToString: false,
+       ioRegistries: [org.apache.hugegraph.io.HugeGraphIoRegistry]
+     }
+  }
+  - {className: org.apache.tinkerpop.gremlin.util.ser.GraphSONMessageSerializerV2,
+     config: {
+       serializeResultToString: false,
+       ioRegistries: [org.apache.hugegraph.io.HugeGraphIoRegistry]
+     }
+  }
+  - {className: org.apache.tinkerpop.gremlin.util.ser.GraphSONMessageSerializerV3,
      config: {
        serializeResultToString: false,
        ioRegistries: [org.apache.hugegraph.io.HugeGraphIoRegistry]
diff --git a/hugegraph-server/hugegraph-dist/src/assembly/travis/start-pd.sh b/hugegraph-server/hugegraph-dist/src/assembly/travis/start-pd.sh
index 35e82ade40..5ebb6c53a5 100755
--- a/hugegraph-server/hugegraph-dist/src/assembly/travis/start-pd.sh
+++ b/hugegraph-server/hugegraph-dist/src/assembly/travis/start-pd.sh
@@ -30,8 +30,12 @@ else
 fi
 
 PD_DIR=$HOME_DIR/hugegraph-pd/apache-hugegraph-pd-$VersionInBash
+TRAVIS_DIR=$(dirname "$0")
+
+source "$TRAVIS_DIR"/ci-service-utils.sh
 
 pushd $PD_DIR
 . bin/start-hugegraph-pd.sh
-sleep 10
+wait_for_http_status HugeGraphPD http://127.0.0.1:8620/v1/health \
+                     "$PD_DIR"/bin/pid "$PD_DIR" 90 200,401
 popd
diff --git a/hugegraph-server/hugegraph-dist/src/assembly/travis/start-store.sh b/hugegraph-server/hugegraph-dist/src/assembly/travis/start-store.sh
index 3e876ce9a0..cb54a03efe 100755
--- a/hugegraph-server/hugegraph-dist/src/assembly/travis/start-store.sh
+++ b/hugegraph-server/hugegraph-dist/src/assembly/travis/start-store.sh
@@ -30,8 +30,12 @@ else
 fi
 
 STORE_DIR=$HOME_DIR/hugegraph-store/apache-hugegraph-store-$VersionInBash
+TRAVIS_DIR=$(dirname "$0")
+
+source "$TRAVIS_DIR"/ci-service-utils.sh
 
 pushd $STORE_DIR
 . bin/start-hugegraph-store.sh
-sleep 10
+wait_for_http_status HugeGraphStore http://127.0.0.1:8520/v1/health \
+                     "$STORE_DIR"/bin/pid "$STORE_DIR" 90 200,401
 popd
diff --git a/hugegraph-server/hugegraph-test/pom.xml b/hugegraph-server/hugegraph-test/pom.xml
index b3fc645f79..bef5496b5a 100644
--- a/hugegraph-server/hugegraph-test/pom.xml
+++ b/hugegraph-server/hugegraph-test/pom.xml
@@ -86,12 +86,6 @@
             gremlin-test
             ${tinkerpop.version}
         
-        
-            org.apache.tinkerpop
-            gremlin-groovy-test
-            3.2.11
-        
-
         
             org.glassfish.jersey.test-framework.providers
             jersey-test-framework-provider-grizzly2
diff --git a/hugegraph-server/hugegraph-test/src/main/java/org/apache/hugegraph/api/CypherApiTest.java b/hugegraph-server/hugegraph-test/src/main/java/org/apache/hugegraph/api/CypherApiTest.java
index 3c3e3049f3..3c111bae3a 100644
--- a/hugegraph-server/hugegraph-test/src/main/java/org/apache/hugegraph/api/CypherApiTest.java
+++ b/hugegraph-server/hugegraph-test/src/main/java/org/apache/hugegraph/api/CypherApiTest.java
@@ -19,8 +19,11 @@
 
 import static org.apache.hugegraph.testutil.Assert.assertContains;
 
+import java.util.List;
 import java.util.Map;
 
+import org.apache.hugegraph.testutil.Assert;
+import org.apache.hugegraph.util.JsonUtil;
 import org.junit.Before;
 import org.junit.Test;
 
@@ -72,13 +75,153 @@ public void testRelationQuery() {
         this.testCypherQueryAndContains(cypher, "friend");
     }
 
-    private void testCypherQueryAndContains(String cypher, String containsText) {
+    @Test
+    public void testReturnNodeIdAsPrimitiveValue() {
+        String cypher = "MATCH (n:person) WHERE n.name = 'marko' " +
+                        "RETURN id(n) AS nodeId";
+
+        String content = this.testCypherQueryAndContains(cypher, "nodeId");
+        List data = assertCypherSuccessData(content);
+        Map row = assertSingleMapRow(data);
+        Object nodeId = row.get("nodeId");
+
+        Assert.assertNotNull(nodeId);
+        assertPrimitiveValue(nodeId);
+        assertNoHugeGraphIdLeak(content);
+    }
+
+    @Test
+    public void testReturnNodeDoesNotLeakInternalIdTypes() {
+        String cypher = "MATCH (n:person) WHERE n.name = 'marko' RETURN n";
+
+        String content = this.testCypherQueryAndContains(cypher, "marko");
+        assertNoHugeGraphIdLeak(content);
+    }
+
+    @Test
+    public void testReturnNestedIdDoesNotLeakInternalIdTypes() {
+        String cypher = "MATCH (n:person) WHERE n.name = 'marko' " +
+                        "RETURN {nodeId: id(n), values: [id(n), n.name]} " +
+                        "AS payload";
+
+        String content = this.testCypherQueryAndContains(cypher, "payload");
+        List data = assertCypherSuccessData(content);
+        Map row = assertSingleMapRow(data);
+        Map payload = assertMapValue(row, "payload");
+        List values = assertListValue(payload, "values");
+        Object nodeId = payload.get("nodeId");
+
+        Assert.assertNotNull(nodeId);
+        assertPrimitiveValue(nodeId);
+        Assert.assertEquals(2, values.size());
+        Assert.assertEquals(nodeId, values.get(0));
+        Assert.assertEquals("marko", values.get(1));
+        assertNoHugeGraphIdLeak(content);
+    }
+
+    @Test
+    public void testReturnRelationIdDoesNotLeakInternalIdTypes() {
+        String cypher = "MATCH (n:person)-[r:knows]->(friend:person) " +
+                        "WHERE n.name = 'marko' RETURN id(r) AS relationId";
+
+        String content = this.testCypherQueryAndContains(cypher, "relationId");
+        List data = assertCypherSuccessData(content);
+        Map row = assertSingleMapRow(data);
+        Object relationId = row.get("relationId");
+
+        Assert.assertNotNull(relationId);
+        assertPrimitiveValue(relationId);
+        assertNoHugeGraphIdLeak(content);
+    }
+
+    @Test
+    public void testReturnPathShape() {
+        String cypher = "MATCH p=(n:person)-[r:knows]->(friend:person) " +
+                        "WHERE n.name = 'marko' RETURN p AS path";
+
+        String content = this.testCypherQueryAndContains(cypher, "path");
+        List data = assertCypherSuccessData(content);
+        Map row = assertSingleMapRow(data);
+        List path = assertListValue(row, "path");
+
+        Assert.assertEquals(3, path.size());
+        Map source = assertMapValue(path, 0);
+        Map relation = assertMapValue(path, 1);
+        Map target = assertMapValue(path, 2);
+
+        Assert.assertEquals("node", source.get("_type"));
+        Assert.assertEquals("person", source.get("_label"));
+        Assert.assertEquals("marko", source.get("name"));
+        Assert.assertEquals("knows", relation.get("_label"));
+        Assert.assertEquals("node", target.get("_type"));
+        Assert.assertEquals("person", target.get("_label"));
+        Assert.assertEquals("peter", target.get("name"));
+        assertContains("marko", content);
+        assertContains("peter", content);
+        assertNoHugeGraphIdLeak(content);
+    }
+
+    private String testCypherQueryAndContains(String cypher,
+                                             String containsText) {
         Response r = client().post(PATH, cypher);
-        this.validStatusAndTextContains(containsText, r);
+        return this.validStatusAndTextContains(containsText, r);
     }
 
-    private void validStatusAndTextContains(String value, Response r) {
+    private String validStatusAndTextContains(String value, Response r) {
         String content = assertResponseStatus(200, r);
         assertContains(value, content);
+        return content;
+    }
+
+    private static void assertNoHugeGraphIdLeak(String content) {
+        Assert.assertFalse(content.contains("org.apache.hugegraph.backend.id"));
+        Assert.assertFalse(content.contains("StringId"));
+        Assert.assertFalse(content.contains("LongId"));
+        Assert.assertFalse(content.contains("UuidId"));
+        Assert.assertFalse(content.contains("EdgeId"));
+    }
+
+    @SuppressWarnings("unchecked")
+    private static List assertCypherSuccessData(String content) {
+        Map response = JsonUtil.fromJson(content, Map.class);
+        Assert.assertTrue(response.containsKey("requestId"));
+
+        Map status = assertMapValue(response, "status");
+        Assert.assertEquals(200, ((Number) status.get("code")).intValue());
+        Assert.assertEquals("", status.get("message"));
+
+        Map result = assertMapValue(response, "result");
+        Assert.assertInstanceOf(List.class, result.get("data"));
+        Assert.assertInstanceOf(Map.class, result.get("meta"));
+        return (List) result.get("data");
+    }
+
+    private static Map assertSingleMapRow(List data) {
+        Assert.assertEquals(1, data.size());
+        Assert.assertInstanceOf(Map.class, data.get(0));
+        return (Map) data.get(0);
+    }
+
+    private static Map assertMapValue(Map map, String key) {
+        Assert.assertTrue(map.containsKey(key));
+        Assert.assertInstanceOf(Map.class, map.get(key));
+        return (Map) map.get(key);
+    }
+
+    private static Map assertMapValue(List list, int index) {
+        Assert.assertTrue(list.size() > index);
+        Assert.assertInstanceOf(Map.class, list.get(index));
+        return (Map) list.get(index);
+    }
+
+    private static List assertListValue(Map map, String key) {
+        Assert.assertTrue(map.containsKey(key));
+        Assert.assertInstanceOf(List.class, map.get(key));
+        return (List) map.get(key);
+    }
+
+    private static void assertPrimitiveValue(Object value) {
+        Assert.assertFalse(value instanceof Map);
+        Assert.assertFalse(value instanceof List);
     }
 }
diff --git a/hugegraph-server/hugegraph-test/src/main/java/org/apache/hugegraph/api/cypher/CypherClientTest.java b/hugegraph-server/hugegraph-test/src/main/java/org/apache/hugegraph/api/cypher/CypherClientTest.java
new file mode 100644
index 0000000000..d9d36638d5
--- /dev/null
+++ b/hugegraph-server/hugegraph-test/src/main/java/org/apache/hugegraph/api/cypher/CypherClientTest.java
@@ -0,0 +1,129 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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 org.apache.hugegraph.api.cypher;
+
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import org.apache.hugegraph.backend.id.IdGenerator;
+import org.apache.hugegraph.testutil.Assert;
+import org.apache.hugegraph.unit.BaseUnitTest;
+import org.apache.tinkerpop.gremlin.process.traversal.Path;
+import org.apache.tinkerpop.gremlin.process.traversal.step.util.MutablePath;
+import org.junit.Test;
+
+public class CypherClientTest extends BaseUnitTest {
+
+    @Test
+    public void testNormalizeHandlesNullMapAndArrayValues() {
+        Map value = new LinkedHashMap<>();
+        value.put(IdGenerator.of(1L),
+                  new Object[]{IdGenerator.of("marko"), null});
+
+        Object normalized = CypherClient.normalize(value);
+
+        Assert.assertInstanceOf(Map.class, normalized);
+        Map map = (Map) normalized;
+        Assert.assertTrue(map.containsKey(1L));
+        Assert.assertInstanceOf(List.class, map.get(1L));
+
+        List values = (List) map.get(1L);
+        Assert.assertEquals("marko", values.get(0));
+        Assert.assertNull(values.get(1));
+    }
+
+    @Test
+    public void testNormalizeHandlesCyclicReferences() {
+        Map value = new LinkedHashMap<>();
+        value.put("private-value", value);
+
+        Assert.assertThrows(IllegalArgumentException.class,
+                            () -> CypherClient.normalize(value), e -> {
+            Assert.assertContains("cyclic Cypher result", e.getMessage());
+            Assert.assertFalse(e.getMessage().contains("private-value"));
+        });
+    }
+
+    @Test
+    public void testNormalizePreservesThirtyTwoContainerLayers() {
+        Object value = "leaf";
+        for (int i = 0; i < 32; i++) {
+            value = new Object[]{value};
+        }
+
+        Object normalized = CypherClient.normalize(value);
+        Object current = normalized;
+        for (int i = 0; i < 32; i++) {
+            Assert.assertInstanceOf(List.class, current);
+            List list = (List) current;
+            Assert.assertEquals(1, list.size());
+            current = list.get(0);
+        }
+
+        Assert.assertEquals("leaf", current);
+    }
+
+    @Test
+    public void testNormalizeRejectsThirtyThirdContainerLayer() {
+        Object value = "leaf";
+        for (int i = 0; i < 33; i++) {
+            value = new Object[]{value};
+        }
+        Object deeplyNestedValue = value;
+
+        Assert.assertThrows(IllegalArgumentException.class,
+                            () -> CypherClient.normalize(deeplyNestedValue),
+                            e -> Assert.assertContains(
+                                    "max normalization depth 32",
+                                    e.getMessage()));
+    }
+
+    @Test
+    public void testNormalizePreservesPathLabelsAndObjects() {
+        Path path = MutablePath.make()
+                               .extend(IdGenerator.of("marko"),
+                                       Set.of("a"))
+                               .extend(IdGenerator.of("lop"),
+                                       Set.of("b", "software"));
+
+        Object normalized = CypherClient.normalize(path);
+
+        Assert.assertInstanceOf(Map.class, normalized);
+        Map map = (Map) normalized;
+        Assert.assertTrue(map.containsKey("labels"));
+        Assert.assertTrue(map.containsKey("objects"));
+
+        Assert.assertInstanceOf(List.class, map.get("labels"));
+        Assert.assertInstanceOf(List.class, map.get("objects"));
+
+        List labels = (List) map.get("labels");
+        List objects = (List) map.get("objects");
+        Assert.assertEquals(2, labels.size());
+        Assert.assertEquals(2, objects.size());
+
+        Assert.assertEquals("marko", objects.get(0));
+        Assert.assertEquals("lop", objects.get(1));
+        List firstLabels = (List) labels.get(0);
+        List secondLabels = (List) labels.get(1);
+        Assert.assertTrue(firstLabels.contains("a"));
+        Assert.assertTrue(secondLabels.contains("b"));
+        Assert.assertTrue(secondLabels.contains("software"));
+    }
+}
diff --git a/hugegraph-server/hugegraph-test/src/main/java/org/apache/hugegraph/core/CountStrategyCoreTest.java b/hugegraph-server/hugegraph-test/src/main/java/org/apache/hugegraph/core/CountStrategyCoreTest.java
index 660c2e040c..7e8bbdfb2d 100644
--- a/hugegraph-server/hugegraph-test/src/main/java/org/apache/hugegraph/core/CountStrategyCoreTest.java
+++ b/hugegraph-server/hugegraph-test/src/main/java/org/apache/hugegraph/core/CountStrategyCoreTest.java
@@ -17,9 +17,13 @@
 
 package org.apache.hugegraph.core;
 
+import java.util.HashSet;
+import java.util.Set;
+
 import org.apache.hugegraph.exception.NoIndexException;
 import org.apache.hugegraph.schema.SchemaManager;
 import org.apache.hugegraph.testutil.Assert;
+import org.apache.hugegraph.traversal.optimize.HugeCountStep;
 import org.apache.hugegraph.traversal.optimize.HugeGraphStep;
 import org.apache.tinkerpop.gremlin.process.traversal.P;
 import org.apache.tinkerpop.gremlin.process.traversal.Step;
@@ -209,6 +213,47 @@ public void testWhereCountGteNegativeDoesNotBuildInvalidRange() {
         Assert.assertEquals(4L, count);
     }
 
+    @Test
+    public void testOptimizedGraphCountCanBeResetAndReused() {
+        this.initSchema();
+        this.initGraph();
+
+        GraphTraversal traversal = graph().traversal().V().count();
+
+        Assert.assertEquals(3L, traversal.next());
+
+        traversal.asAdmin().reset();
+
+        Assert.assertEquals(3L, traversal.next());
+    }
+
+    @Test
+    public void testOptimizedGraphCountEqualityIgnoresExecutionState() {
+        this.initSchema();
+        this.initGraph();
+
+        GraphTraversal first = graph().traversal().V().count();
+        GraphTraversal second = graph().traversal().V().count();
+        first.asAdmin().applyStrategies();
+        second.asAdmin().applyStrategies();
+
+        Step firstStep = first.asAdmin().getEndStep();
+        Step secondStep = second.asAdmin().getEndStep();
+        Assert.assertInstanceOf(HugeCountStep.class, firstStep);
+        Assert.assertInstanceOf(HugeCountStep.class, secondStep);
+        Assert.assertEquals(firstStep, secondStep);
+
+        int hashCode = firstStep.hashCode();
+        Set> steps = new HashSet<>();
+        steps.add(firstStep);
+
+        Assert.assertEquals(3L, first.next());
+
+        Assert.assertEquals(hashCode, firstStep.hashCode());
+        Assert.assertEquals(firstStep, secondStep);
+        Assert.assertTrue(steps.contains(firstStep));
+    }
+
     @Test
     public void testRepeatAfterTextRangeFilterWithEmptyResult() {
         this.initTextRangeSchema(true);
diff --git a/hugegraph-server/hugegraph-test/src/main/java/org/apache/hugegraph/core/VertexCoreTest.java b/hugegraph-server/hugegraph-test/src/main/java/org/apache/hugegraph/core/VertexCoreTest.java
index 4b7ec65bc3..05bc8f847c 100644
--- a/hugegraph-server/hugegraph-test/src/main/java/org/apache/hugegraph/core/VertexCoreTest.java
+++ b/hugegraph-server/hugegraph-test/src/main/java/org/apache/hugegraph/core/VertexCoreTest.java
@@ -3270,6 +3270,24 @@ public void testQueryByLabel() {
                                            SplicingIdGenerator.splicing(bookId, "java-5")));
     }
 
+    @Test
+    public void testQueryByNullKeyAndLabel() {
+        HugeGraph graph = graph();
+        init10Vertices();
+
+        Assert.assertFalse(graph.traversal().V()
+                                .has((String) null, "test-null-key")
+                                .hasNext());
+        Assert.assertFalse(graph.traversal().V()
+                                .hasLabel((String) null)
+                                .hasNext());
+
+        List vertices = graph.traversal().V()
+                                     .hasLabel(null, "book")
+                                     .toList();
+        Assert.assertEquals(5, vertices.size());
+    }
+
     @Test
     public void testQueryByLabelWithLimit() {
         HugeGraph graph = graph();
diff --git a/hugegraph-server/hugegraph-test/src/main/java/org/apache/hugegraph/tinkerpop/ProcessBasicSuite.java b/hugegraph-server/hugegraph-test/src/main/java/org/apache/hugegraph/tinkerpop/ProcessBasicSuite.java
index e0fcba9832..d899c86f8f 100644
--- a/hugegraph-server/hugegraph-test/src/main/java/org/apache/hugegraph/tinkerpop/ProcessBasicSuite.java
+++ b/hugegraph-server/hugegraph-test/src/main/java/org/apache/hugegraph/tinkerpop/ProcessBasicSuite.java
@@ -26,6 +26,8 @@
 import org.apache.tinkerpop.gremlin.process.traversal.TraversalEngine;
 import org.apache.tinkerpop.gremlin.process.traversal.TraversalInterruptionTest;
 import org.apache.tinkerpop.gremlin.process.traversal.step.ComplexTest;
+import org.apache.tinkerpop.gremlin.process.traversal.step.OrderabilityTest;
+import org.apache.tinkerpop.gremlin.process.traversal.step.TernaryBooleanLogicsTest;
 import org.apache.tinkerpop.gremlin.process.traversal.step.branch.BranchTest;
 import org.apache.tinkerpop.gremlin.process.traversal.step.branch.ChooseTest;
 import org.apache.tinkerpop.gremlin.process.traversal.step.branch.LocalTest;
@@ -51,6 +53,7 @@
 import org.apache.tinkerpop.gremlin.process.traversal.step.map.CoalesceTest;
 import org.apache.tinkerpop.gremlin.process.traversal.step.map.ConstantTest;
 import org.apache.tinkerpop.gremlin.process.traversal.step.map.CountTest;
+import org.apache.tinkerpop.gremlin.process.traversal.step.map.ElementMapTest;
 import org.apache.tinkerpop.gremlin.process.traversal.step.map.FlatMapTest;
 import org.apache.tinkerpop.gremlin.process.traversal.step.map.FoldTest;
 import org.apache.tinkerpop.gremlin.process.traversal.step.map.GraphTest;
@@ -61,6 +64,8 @@
 import org.apache.tinkerpop.gremlin.process.traversal.step.map.MathTest;
 import org.apache.tinkerpop.gremlin.process.traversal.step.map.MaxTest;
 import org.apache.tinkerpop.gremlin.process.traversal.step.map.MeanTest;
+import org.apache.tinkerpop.gremlin.process.traversal.step.map.MergeEdgeTest;
+import org.apache.tinkerpop.gremlin.process.traversal.step.map.MergeVertexTest;
 import org.apache.tinkerpop.gremlin.process.traversal.step.map.MinTest;
 import org.apache.tinkerpop.gremlin.process.traversal.step.map.OrderTest;
 import org.apache.tinkerpop.gremlin.process.traversal.step.map.PathTest;
@@ -87,6 +92,7 @@
 import org.apache.tinkerpop.gremlin.process.traversal.strategy.decoration.ElementIdStrategyProcessTest;
 import org.apache.tinkerpop.gremlin.process.traversal.strategy.decoration.EventStrategyProcessTest;
 import org.apache.tinkerpop.gremlin.process.traversal.strategy.decoration.PartitionStrategyProcessTest;
+import org.apache.tinkerpop.gremlin.process.traversal.strategy.decoration.SeedStrategyProcessTest;
 import org.apache.tinkerpop.gremlin.process.traversal.strategy.decoration.SubgraphStrategyProcessTest;
 import org.apache.tinkerpop.gremlin.process.traversal.strategy.decoration.TranslationStrategyProcessTest;
 import org.apache.tinkerpop.gremlin.process.traversal.strategy.optimization.EarlyLimitStrategyProcessTest;
@@ -105,7 +111,9 @@ public class ProcessBasicSuite extends AbstractGremlinSuite {
 
     /**
      * This list of tests in the suite that will be executed
-     * as part of this suite.
+     * as part of this suite. It is synchronized with TinkerPop 3.7.6's
+     * official ProcessStandardSuite; HugeGraphWriteTest
+     * intentionally replaces WriteTest.Traversals.
      */
     private static final Class[] ALL_TESTS = new Class[]{
             // branch
@@ -138,6 +146,7 @@ public class ProcessBasicSuite extends AbstractGremlinSuite {
             CoalesceTest.Traversals.class,
             ConstantTest.Traversals.class,
             CountTest.Traversals.class,
+            ElementMapTest.Traversals.class,
             FlatMapTest.Traversals.class,
             FoldTest.Traversals.class,
             GraphTest.Traversals.class,
@@ -149,6 +158,8 @@ public class ProcessBasicSuite extends AbstractGremlinSuite {
             MathTest.Traversals.class,
             MaxTest.Traversals.class,
             MeanTest.Traversals.class,
+            MergeEdgeTest.Traversals.class,
+            MergeVertexTest.Traversals.class,
             MinTest.Traversals.class,
             SumTest.Traversals.class,
             OrderTest.Traversals.class,
@@ -161,7 +172,7 @@ public class ProcessBasicSuite extends AbstractGremlinSuite {
             VertexTest.Traversals.class,
             UnfoldTest.Traversals.class,
             ValueMapTest.Traversals.class,
-            // Override WriteTest.Traversals.class
+            // Intentionally replace WriteTest.Traversals.class
             HugeGraphWriteTest.class,
 
             // sideEffect
@@ -190,11 +201,16 @@ public class ProcessBasicSuite extends AbstractGremlinSuite {
             EventStrategyProcessTest.class,
             ReadOnlyStrategyProcessTest.class,
             PartitionStrategyProcessTest.class,
+            SeedStrategyProcessTest.class,
             SubgraphStrategyProcessTest.class,
 
             // optimizations
             IncidentToAdjacentStrategyProcessTest.class,
-            EarlyLimitStrategyProcessTest.class
+            EarlyLimitStrategyProcessTest.class,
+
+            // semantics
+            OrderabilityTest.Traversals.class,
+            TernaryBooleanLogicsTest.class
     };
 
     /**
@@ -232,6 +248,7 @@ public class ProcessBasicSuite extends AbstractGremlinSuite {
             CoalesceTest.class,
             ConstantTest.class,
             CountTest.class,
+            ElementMapTest.class,
             FlatMapTest.class,
             FoldTest.class,
             LoopsTest.class,
diff --git a/hugegraph-server/hugegraph-test/src/main/java/org/apache/hugegraph/tinkerpop/StructureBasicSuite.java b/hugegraph-server/hugegraph-test/src/main/java/org/apache/hugegraph/tinkerpop/StructureBasicSuite.java
index 593e89359e..ea5df1ab96 100644
--- a/hugegraph-server/hugegraph-test/src/main/java/org/apache/hugegraph/tinkerpop/StructureBasicSuite.java
+++ b/hugegraph-server/hugegraph-test/src/main/java/org/apache/hugegraph/tinkerpop/StructureBasicSuite.java
@@ -30,6 +30,7 @@
 import org.apache.tinkerpop.gremlin.structure.GraphTest;
 import org.apache.tinkerpop.gremlin.structure.PropertyTest;
 import org.apache.tinkerpop.gremlin.structure.SerializationTest;
+import org.apache.tinkerpop.gremlin.structure.TransactionMultiThreadedTest;
 import org.apache.tinkerpop.gremlin.structure.TransactionTest;
 import org.apache.tinkerpop.gremlin.structure.VariablesTest;
 import org.apache.tinkerpop.gremlin.structure.VertexPropertyTest;
@@ -64,7 +65,8 @@ public class StructureBasicSuite extends AbstractGremlinSuite {
     /**
      * This list of tests in the suite that will be executed.
      * Gremlin developers should add to this list
-     * as needed to enforce tests upon implementations.
+     * as needed to enforce tests upon implementations. This list is synchronized
+     * with TinkerPop 3.7.6's official StructureStandardSuite.
      */
     private static final Class[] ALL_TESTS = new Class[]{
             CommunityGeneratorTest.class,
@@ -94,6 +96,7 @@ public class StructureBasicSuite extends AbstractGremlinSuite {
             SerializationTest.class,
             StarGraphTest.class,
             TransactionTest.class,
+            TransactionMultiThreadedTest.class,
             VertexTest.class
     };
 
diff --git a/hugegraph-server/hugegraph-test/src/main/java/org/apache/hugegraph/tinkerpop/TestGraph.java b/hugegraph-server/hugegraph-test/src/main/java/org/apache/hugegraph/tinkerpop/TestGraph.java
index 9ef6d9affd..793b823c6b 100644
--- a/hugegraph-server/hugegraph-test/src/main/java/org/apache/hugegraph/tinkerpop/TestGraph.java
+++ b/hugegraph-server/hugegraph-test/src/main/java/org/apache/hugegraph/tinkerpop/TestGraph.java
@@ -430,6 +430,7 @@ public void initModernSchema(IdStrategy idStrategy) {
         SchemaManager schema = this.graph.schema();
 
         schema.propertyKey("weight").asDouble().ifNotExist().create();
+        schema.propertyKey("a").asInt().ifNotExist().create();
         schema.propertyKey("name").ifNotExist().create();
         schema.propertyKey("lang").ifNotExist().create();
         schema.propertyKey("age").asInt().ifNotExist().create();
@@ -499,12 +500,12 @@ public void initModernSchema(IdStrategy idStrategy) {
         }
 
         schema.edgeLabel("knows").link("person", "person")
-              .properties("weight", "year")
-              .nullableKeys("weight", "year")
+              .properties("weight", "year", "a")
+              .nullableKeys("weight", "year", "a")
               .ifNotExist().create();
         schema.edgeLabel("created").link("person", "software")
-              .properties("weight")
-              .nullableKeys("weight")
+              .properties("weight", "a")
+              .nullableKeys("weight", "a")
               .ifNotExist().create();
         schema.edgeLabel("codeveloper").link("person", "person")
               .properties("year")
@@ -591,9 +592,15 @@ public void initClassicSchema(IdStrategy idStrategy) {
 
     @Watched
     public void initBasicSchema(IdStrategy idStrategy, String defaultVL) {
+        this.initBasicSchema(idStrategy, defaultVL, defaultVL);
+    }
+
+    @Watched
+    public void initBasicSchema(IdStrategy idStrategy, String defaultVL,
+                                String selfVL) {
         this.initBasicPropertyKey();
         this.initBasicVertexLabelV(idStrategy, defaultVL);
-        this.initBasicVertexLabelAndEdgeLabelExceptV(defaultVL);
+        this.initBasicVertexLabelAndEdgeLabelExceptV(defaultVL, selfVL);
     }
 
     @Watched
@@ -603,7 +610,9 @@ private void initBasicPropertyKey() {
         schema.propertyKey("__id").ifNotExist().create();
         schema.propertyKey("oid").asInt().ifNotExist().create();
         schema.propertyKey("communityIndex").asInt().ifNotExist().create();
-        schema.propertyKey("test").ifNotExist().create();
+        if (!this.graph.existsPropertyKey("test")) {
+            schema.propertyKey("test").ifNotExist().create();
+        }
         schema.propertyKey("testing").ifNotExist().create();
         schema.propertyKey("data").ifNotExist().create();
         schema.propertyKey("name").ifNotExist().create();
@@ -748,7 +757,8 @@ private void initBasicVertexLabelV(IdStrategy idStrategy, String defaultVL) {
     }
 
     @Watched
-    private void initBasicVertexLabelAndEdgeLabelExceptV(String defaultVL) {
+    private void initBasicVertexLabelAndEdgeLabelExceptV(String defaultVL,
+                                                         String selfVL) {
         SchemaManager schema = this.graph.schema();
 
         if (!"person".equals(defaultVL)) {
@@ -770,7 +780,7 @@ private void initBasicVertexLabelAndEdgeLabelExceptV(String defaultVL) {
               .nullableKeys("test")
               .ifNotExist().create();
 
-        schema.edgeLabel("self").link(defaultVL, defaultVL)
+        schema.edgeLabel("self").link(selfVL, selfVL)
               .properties("__id", "test", "name", "some", "acl", "weight",
                           "here", "to-change", "dropped", "not-dropped", "new",
                           "to-drop", "short", "long")
diff --git a/hugegraph-server/hugegraph-test/src/main/java/org/apache/hugegraph/tinkerpop/TestGraphProvider.java b/hugegraph-server/hugegraph-test/src/main/java/org/apache/hugegraph/tinkerpop/TestGraphProvider.java
index c257e8bd1c..a8b07817a1 100644
--- a/hugegraph-server/hugegraph-test/src/main/java/org/apache/hugegraph/tinkerpop/TestGraphProvider.java
+++ b/hugegraph-server/hugegraph-test/src/main/java/org/apache/hugegraph/tinkerpop/TestGraphProvider.java
@@ -47,11 +47,13 @@
 import org.apache.tinkerpop.gremlin.FeatureRequirements;
 import org.apache.tinkerpop.gremlin.LoadGraphWith;
 import org.apache.tinkerpop.gremlin.process.traversal.dsl.graph.GraphTraversalSource;
+import org.apache.tinkerpop.gremlin.process.traversal.step.map.MergeEdgeTest;
 import org.apache.tinkerpop.gremlin.process.traversal.strategy.optimization.LazyBarrierStrategy;
 import org.apache.tinkerpop.gremlin.structure.Element;
 import org.apache.tinkerpop.gremlin.structure.Graph;
 import org.apache.tinkerpop.gremlin.structure.Graph.Features.VertexPropertyFeatures;
 import org.apache.tinkerpop.gremlin.structure.Transaction;
+import org.apache.tinkerpop.gremlin.structure.TransactionMultiThreadedTest;
 import org.junit.Assert;
 import org.junit.Assume;
 import org.slf4j.Logger;
@@ -85,10 +87,19 @@ public class TestGraphProvider extends AbstractGraphProvider {
     private static final String GREMLIN_GRAPH_KEY = "gremlin.graph";
     private static final String GREMLIN_GRAPH_VALUE =
             "org.apache.hugegraph.tinkerpop.TestGraphFactory";
+    private static final String BACKEND = "backend";
+    private static final String BACKEND_ROCKSDB = "rocksdb";
+    private static final String ROCKSDB_DATA_PATH = "rocksdb.data_path";
+    private static final String ROCKSDB_WAL_PATH = "rocksdb.wal_path";
+    private static final String ROCKSDB_DATA_DISKS = "rocksdb.data_disks";
+    private static final String TEST_PATH_SEPARATOR = "/";
+    private static final int MAX_ROCKSDB_PATH_SUFFIX_PREFIX_LENGTH = 80;
 
     private static final String AKEY_CLASS_PREFIX =
             "org.apache.tinkerpop.gremlin.structure." +
             "PropertyTest.PropertyFeatureSupportTest";
+    private static final String SUPPORTS_PREFIX = "supports";
+    private static final String FEATURE_VALUES_SUFFIX = "Values";
     private static final String IO_CLASS_PREFIX =
             "org.apache.tinkerpop.gremlin.structure.io.IoGraphTest";
     private static final String IO_TEST_PREFIX =
@@ -182,8 +193,17 @@ public Map getBaseConfiguration(
             confMap.put(key, config.getProperty(key));
         }
         String storePrefix = config.getString(CoreOptions.STORE.name());
-        confMap.put(CoreOptions.STORE.name(),
-                    storePrefix + "_" + this.suite + "_" + graphName);
+        String store = storePrefix + "_" + this.suite + "_" + graphName;
+        if (isTransactionMultiThreadedPropertyTest(testClass, testMethod)) {
+            store += "_txprop";
+        } else if (isMergeEdgeSelfTest(testClass, testMethod)) {
+            store += "_meself";
+        }
+        confMap.put(CoreOptions.STORE.name(), store);
+        if (isRocksDBBackend(config)) {
+            this.isolateRocksDBPaths(confMap, graphName, testClass,
+                                     testMethod);
+        }
         confMap.put(GREMLIN_GRAPH_KEY, GREMLIN_GRAPH_VALUE);
         confMap.put(TEST_CLASS, testClass);
         confMap.put(TEST_METHOD, testMethod);
@@ -193,6 +213,90 @@ public Map getBaseConfiguration(
         return confMap;
     }
 
+    private void isolateRocksDBPaths(Map confMap,
+                                     String graphName, Class testClass,
+                                     String testMethod) {
+        String testClassName = testClass.getName();
+        String rawSuffix = this.suite + "_" + graphName + "_" +
+                           testClassName + "_" + testMethod;
+        String prefix = sanitizePathPart(this.suite + "_" + graphName + "_" +
+                                         testClass.getSimpleName() + "_" +
+                                         testMethod);
+        if (prefix.length() > MAX_ROCKSDB_PATH_SUFFIX_PREFIX_LENGTH) {
+            prefix = prefix.substring(0,
+                                      MAX_ROCKSDB_PATH_SUFFIX_PREFIX_LENGTH);
+        }
+        String pathSuffix = prefix + "_" + shortHash(rawSuffix);
+        isolatePath(confMap, ROCKSDB_DATA_PATH, pathSuffix);
+        isolatePath(confMap, ROCKSDB_WAL_PATH, pathSuffix);
+
+        Object dataDisks = confMap.get(ROCKSDB_DATA_DISKS);
+        if (dataDisks != null) {
+            confMap.put(ROCKSDB_DATA_DISKS,
+                        isolateDataDisks(dataDisks, pathSuffix));
+        }
+    }
+
+    private static void isolatePath(Map confMap, String key,
+                                    String pathSuffix) {
+        Object path = confMap.get(key);
+        if (path == null) {
+            return;
+        }
+        confMap.put(key, appendPath(path.toString(), pathSuffix));
+    }
+
+    private static String isolateDataDisks(Object dataDisks,
+                                           String pathSuffix) {
+        String value = dataDisks.toString().trim();
+        if (value.isEmpty()) {
+            return value;
+        }
+
+        boolean wrapped = value.startsWith("[") && value.endsWith("]");
+        String body = wrapped ? value.substring(1, value.length() - 1) : value;
+        String[] entries = body.split(",");
+        StringBuilder builder = new StringBuilder();
+        for (String entry : entries) {
+            String item = entry.trim();
+            int index = item.indexOf(':');
+            if (index < 0) {
+                return value;
+            }
+            String table = item.substring(0, index).trim();
+            String path = item.substring(index + 1).trim();
+            if (table.isEmpty() || path.isEmpty()) {
+                return value;
+            }
+            if (builder.length() > 0) {
+                builder.append(',');
+            }
+            builder.append(table).append(':')
+                   .append(appendPath(path, pathSuffix));
+        }
+        return wrapped ? "[" + builder + "]" : builder.toString();
+    }
+
+    private static String appendPath(String path, String suffix) {
+        if (path.endsWith("/") || path.endsWith("\\")) {
+            return path + suffix;
+        }
+        return path + TEST_PATH_SEPARATOR + suffix;
+    }
+
+    private static String sanitizePathPart(String value) {
+        return value.replaceAll("[^A-Za-z0-9._-]", "_");
+    }
+
+    private static String shortHash(String value) {
+        return Integer.toHexString(value.hashCode());
+    }
+
+    private static boolean isRocksDBBackend(Configuration config) {
+        return config != null &&
+               BACKEND_ROCKSDB.equals(config.getString(BACKEND, ""));
+    }
+
     private static boolean customizedId(Class test, String testMethod) {
         Method method;
         try {
@@ -215,10 +319,41 @@ private static boolean customizedId(Class test, String testMethod) {
         return false;
     }
 
+    private static boolean isTransactionMultiThreadedPropertyTest(
+            Class testClass, String testMethod) {
+        return testClass == TransactionMultiThreadedTest.class &&
+               testMethod.equals("shouldChangeVertexProperty");
+    }
+
+    private static boolean isMergeEdgeSelfTest(Class testClass,
+                                               String testMethod) {
+        return testClass == MergeEdgeTest.Traversals.class &&
+               testMethod.equals("g_V_mergeEXlabel_self_weight_05X");
+    }
+
     private static String getAKeyType(Class clazz, String method) {
         if (clazz.getCanonicalName().startsWith(AKEY_CLASS_PREFIX)) {
-            return method.substring(method.indexOf('[') + 9,
-                                    method.indexOf('(') - 6);
+            String feature = method;
+            int featureStart = method.indexOf('[');
+            int featureEnd = method.indexOf(']');
+            if (featureStart >= 0 && featureEnd > featureStart) {
+                feature = method.substring(featureStart + 1, featureEnd);
+            }
+
+            if (!feature.startsWith(SUPPORTS_PREFIX)) {
+                return null;
+            }
+            feature = feature.substring(SUPPORTS_PREFIX.length());
+
+            int valueStart = feature.indexOf('(');
+            if (valueStart >= 0) {
+                feature = feature.substring(0, valueStart);
+            }
+            if (!feature.endsWith(FEATURE_VALUES_SUFFIX)) {
+                return null;
+            }
+            return feature.substring(0, feature.length() -
+                                        FEATURE_VALUES_SUFFIX.length());
         }
         return null;
     }
@@ -292,8 +427,15 @@ public Graph openTestGraph(final Configuration config) {
             testGraph.initPropertyKey("long", "Long");
         }
 
+        if (isTransactionMultiThreadedPropertyTest(testClass, testMethod)) {
+            testGraph.initPropertyKey("test", "Integer");
+        }
+
         // Basic schema is initiated by default once a graph is open
-        testGraph.initBasicSchema(idStrategy(config), TestGraph.DEFAULT_VL);
+        String selfVL = isMergeEdgeSelfTest(testClass, testMethod) ?
+                        "person" : TestGraph.DEFAULT_VL;
+        testGraph.initBasicSchema(idStrategy(config), TestGraph.DEFAULT_VL,
+                                  selfVL);
         if (testClass.getName().equals(
                 "org.apache.tinkerpop.gremlin.process.traversal.step.map.ReadTest$Traversals")) {
             testGraph.initEdgeLabelPersonKnowsPerson();
@@ -330,6 +472,10 @@ public void clear(Graph graph, Configuration config) throws Exception {
         String graphName = config.getString(CoreOptions.STORE.name());
         if (!testGraph.initedBackend()) {
             testGraph.close();
+            if (this.graphs.get(graphName) == testGraph) {
+                this.graphs.remove(graphName);
+            }
+            return;
         }
         if (testGraph.closed()) {
             if (this.graphs.get(graphName) == testGraph) {
@@ -349,6 +495,13 @@ public void clear(Graph graph, Configuration config) throws Exception {
         Class testClass = (Class) config.getProperty(TEST_CLASS);
         testGraph.clearAll(testClass.getCanonicalName());
 
+        if (isRocksDBBackend(config)) {
+            testGraph.close();
+            if (this.graphs.get(graphName) == testGraph) {
+                this.graphs.remove(graphName);
+            }
+        }
+
         LOG.debug("Clear graph '{}'", graphName);
     }
 
diff --git a/hugegraph-server/hugegraph-test/src/main/java/org/apache/hugegraph/traversal/optimize/TraversalUtilOptimizeTest.java b/hugegraph-server/hugegraph-test/src/main/java/org/apache/hugegraph/traversal/optimize/TraversalUtilOptimizeTest.java
index c79db5056f..affe484c0a 100644
--- a/hugegraph-server/hugegraph-test/src/main/java/org/apache/hugegraph/traversal/optimize/TraversalUtilOptimizeTest.java
+++ b/hugegraph-server/hugegraph-test/src/main/java/org/apache/hugegraph/traversal/optimize/TraversalUtilOptimizeTest.java
@@ -57,6 +57,40 @@ public void testCanExtractHasContainerWithoutGraph() {
                 null, new HasContainer("~id", P.eq("1"))));
         Assert.assertFalse(TraversalUtil.canExtractHasContainer(
                 null, new HasContainer("name", P.eq("marko"))));
+        Assert.assertFalse(TraversalUtil.canExtractHasContainer(
+                null, new HasContainer(null, P.eq("marko"))));
+        Assert.assertFalse(TraversalUtil.canExtractHasContainer(
+                null, new HasContainer(T.label.getAccessor(), P.eq(null))));
+        Assert.assertFalse(TraversalUtil.canExtractHasContainer(
+                null, new HasContainer(T.label.getAccessor(),
+                                       P.within(null, "person"))));
+    }
+
+    @Test
+    public void testExtractHasContainerKeepsNullKeyLocal() {
+        Traversal.Admin traversal = __.V()
+                                           .has((String) null,
+                                                "test-null-key")
+                                           .asAdmin();
+        HugeGraphStep newStep = replaceGraphStep(traversal);
+
+        TraversalUtil.extractHasContainer(newStep, traversal);
+
+        Assert.assertTrue(newStep.getHasContainers().isEmpty());
+        Assert.assertTrue(hasStepExists(traversal));
+    }
+
+    @Test
+    public void testExtractHasContainerKeepsMixedNullLabelLocal() {
+        Traversal.Admin traversal = __.V()
+                                           .hasLabel(null, "person")
+                                           .asAdmin();
+        HugeGraphStep newStep = replaceGraphStep(traversal);
+
+        TraversalUtil.extractHasContainer(newStep, traversal);
+
+        Assert.assertTrue(newStep.getHasContainers().isEmpty());
+        Assert.assertTrue(hasStepExists(traversal, T.label.getAccessor()));
     }
 
     @Test
@@ -314,6 +348,11 @@ public void testIsPositiveLabelContainer() {
         Assert.assertFalse(TraversalUtil.isPositiveLabelContainer(
                 new HasContainer(T.label.getAccessor(),
                                  P.within(Collections.emptyList()))));
+        Assert.assertFalse(TraversalUtil.isPositiveLabelContainer(
+                new HasContainer(T.label.getAccessor(), P.eq(null))));
+        Assert.assertFalse(TraversalUtil.isPositiveLabelContainer(
+                new HasContainer(T.label.getAccessor(),
+                                 P.within(null, "person"))));
     }
 
     @Test
diff --git a/hugegraph-server/hugegraph-test/src/main/java/org/apache/hugegraph/unit/UnitTestSuite.java b/hugegraph-server/hugegraph-test/src/main/java/org/apache/hugegraph/unit/UnitTestSuite.java
index 433e75a812..b42e1aec0e 100644
--- a/hugegraph-server/hugegraph-test/src/main/java/org/apache/hugegraph/unit/UnitTestSuite.java
+++ b/hugegraph-server/hugegraph-test/src/main/java/org/apache/hugegraph/unit/UnitTestSuite.java
@@ -17,6 +17,7 @@
 
 package org.apache.hugegraph.unit;
 
+import org.apache.hugegraph.api.cypher.CypherClientTest;
 import org.apache.hugegraph.core.RoleElectionStateMachineTest;
 import org.apache.hugegraph.meta.EtcdMetaDriverTest;
 import org.apache.hugegraph.meta.MetaManagerSchemaCacheClearEventTest;
@@ -32,6 +33,7 @@
 import org.apache.hugegraph.unit.cache.CachedSchemaTransactionTest;
 import org.apache.hugegraph.unit.cache.RamTableTest;
 import org.apache.hugegraph.unit.cassandra.CassandraTest;
+import org.apache.hugegraph.unit.config.GremlinConfigCompatibilityTest;
 import org.apache.hugegraph.unit.core.AnalyzerTest;
 import org.apache.hugegraph.unit.core.BackendMutationTest;
 import org.apache.hugegraph.unit.core.BackendStoreInfoTest;
@@ -40,6 +42,8 @@
 import org.apache.hugegraph.unit.core.DataTypeTest;
 import org.apache.hugegraph.unit.core.DirectionsTest;
 import org.apache.hugegraph.unit.core.ExceptionTest;
+import org.apache.hugegraph.unit.core.GroovyScriptEngineCompatibilityTest;
+import org.apache.hugegraph.unit.core.HugeFeaturesTest;
 import org.apache.hugegraph.unit.core.LocksTableTest;
 import org.apache.hugegraph.unit.core.PageStateTest;
 import org.apache.hugegraph.unit.core.QueryTest;
@@ -67,6 +71,7 @@
 import org.apache.hugegraph.unit.serializer.BinaryScatterSerializerTest;
 import org.apache.hugegraph.unit.serializer.BinarySerializerTest;
 import org.apache.hugegraph.unit.serializer.BytesBufferTest;
+import org.apache.hugegraph.unit.serializer.HugeGraphSONModuleTest;
 import org.apache.hugegraph.unit.serializer.SerializerFactoryTest;
 import org.apache.hugegraph.unit.serializer.StoreSerializerTest;
 import org.apache.hugegraph.unit.serializer.TableBackendEntryTest;
@@ -95,6 +100,7 @@
 
         /* api gremlin */
         GremlinQueryAPITest.class,
+        CypherClientTest.class,
 
         /* api space */
         GraphSpaceAPITest.class,
@@ -127,6 +133,8 @@
         AnalyzerTest.class,
         BackendMutationTest.class,
         ConditionTest.class,
+        GroovyScriptEngineCompatibilityTest.class,
+        HugeFeaturesTest.class,
         ConditionQueryFlattenTest.class,
         QueryTest.class,
         RangeTest.class,
@@ -152,9 +160,13 @@
         BinaryBackendEntryTest.class,
         BinarySerializerTest.class,
         BinaryScatterSerializerTest.class,
+        HugeGraphSONModuleTest.class,
         StoreSerializerTest.class,
         TextSerializerTest.class,
 
+        /* config */
+        GremlinConfigCompatibilityTest.class,
+
         /* cassandra */
         CassandraTest.class,
 
diff --git a/hugegraph-server/hugegraph-test/src/main/java/org/apache/hugegraph/unit/config/GremlinConfigCompatibilityTest.java b/hugegraph-server/hugegraph-test/src/main/java/org/apache/hugegraph/unit/config/GremlinConfigCompatibilityTest.java
new file mode 100644
index 0000000000..292a5cd922
--- /dev/null
+++ b/hugegraph-server/hugegraph-test/src/main/java/org/apache/hugegraph/unit/config/GremlinConfigCompatibilityTest.java
@@ -0,0 +1,582 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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 org.apache.hugegraph.unit.config;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.UUID;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import org.apache.hugegraph.backend.id.EdgeId;
+import org.apache.hugegraph.backend.id.IdGenerator;
+import org.apache.hugegraph.testutil.Assert;
+import org.apache.hugegraph.unit.BaseUnitTest;
+import org.apache.tinkerpop.gremlin.process.traversal.P;
+import org.apache.tinkerpop.gremlin.process.traversal.step.util.MutablePath;
+import org.apache.tinkerpop.gremlin.server.Settings;
+import org.apache.tinkerpop.gremlin.util.MessageSerializer;
+import org.apache.tinkerpop.gremlin.util.message.ResponseMessage;
+import org.apache.tinkerpop.gremlin.util.message.ResponseStatusCode;
+import org.apache.tinkerpop.gremlin.util.ser.MessageTextSerializer;
+import org.junit.Test;
+import org.yaml.snakeyaml.Yaml;
+
+import io.netty.buffer.ByteBuf;
+import io.netty.buffer.ByteBufAllocator;
+
+public class GremlinConfigCompatibilityTest extends BaseUnitTest {
+
+    private static final Pattern CLASS_NAME =
+            Pattern.compile("className:\\s*([^,}\\s]+)");
+    private static final String SERIALIZER_PACKAGE =
+            "org.apache.tinkerpop.gremlin.util.ser.";
+    private static final String GRAPHSON_UNTYPED_V1 =
+            SERIALIZER_PACKAGE + "GraphSONUntypedMessageSerializerV1";
+    private static final String IO_REGISTRY =
+            "org.apache.hugegraph.io.HugeGraphIoRegistry";
+    private static final String GREMLIN_SERVER_CONFIG = "gremlin-server.yaml";
+    private static final String REMOTE_OBJECTS_CONFIG = "remote-objects.yaml";
+    private static final List GREMLIN_SERVER_CONFIG_VARIANTS =
+            Arrays.asList(
+                    "static/conf/gremlin-server.yaml",
+                    "travis/conf-raft1/gremlin-server.yaml",
+                    "travis/conf-raft2/gremlin-server.yaml",
+                    "travis/conf-raft3/gremlin-server.yaml"
+            );
+    private static final List REMOTE_CONFIGS = Arrays.asList(
+            "gremlin-driver-settings.yaml",
+            "remote.yaml",
+            REMOTE_OBJECTS_CONFIG
+    );
+    private static final List TYPED_FALLBACK_SERIALIZERS =
+            Arrays.asList(
+                    SERIALIZER_PACKAGE + "GraphSONMessageSerializerV1",
+                    SERIALIZER_PACKAGE + "GraphSONMessageSerializerV2",
+                    SERIALIZER_PACKAGE + "GraphSONMessageSerializerV3"
+            );
+    private static final List TYPED_GRAPHSON_MIME_TYPES =
+            Arrays.asList(
+                    "application/vnd.gremlin-v1.0+json",
+                    "application/vnd.gremlin-v2.0+json",
+                    "application/vnd.gremlin-v3.0+json"
+            );
+    private static final List UNTYPED_GRAPHSON_MIME_TYPES =
+            Arrays.asList(
+                    "application/vnd.gremlin-v1.0+json;types=false",
+                    "application/vnd.gremlin-v2.0+json;types=false",
+                    "application/vnd.gremlin-v3.0+json;types=false"
+            );
+
+    @Test
+    public void testGremlinServerSerializersUseTinkerPopUtilPackage() throws IOException {
+        String content = readConfig(GREMLIN_SERVER_CONFIG);
+
+        assertUsesHugeGraphIoRegistry(GREMLIN_SERVER_CONFIG, content);
+        assertSerializerClassNamesUseUtilPackage(GREMLIN_SERVER_CONFIG,
+                                                 content);
+    }
+
+    @Test
+    public void testRemoteSerializersUseTinkerPopUtilPackage() throws IOException {
+        for (String file : REMOTE_CONFIGS) {
+            String content = readConfig(file);
+
+            assertUsesHugeGraphIoRegistry(file, content);
+            assertSerializerClassNamesUseUtilPackage(file, content);
+        }
+    }
+
+    @Test
+    public void testConfiguredSerializerClassesAreLoadable() throws Exception {
+        assertConfiguredSerializerClassesAreLoadable(
+                GREMLIN_SERVER_CONFIG, readConfig(GREMLIN_SERVER_CONFIG));
+        for (String file : REMOTE_CONFIGS) {
+            assertConfiguredSerializerClassesAreLoadable(file,
+                                                         readConfig(file));
+        }
+    }
+
+    @Test
+    public void testGremlinServerConfigVariantsSupportGraphSONMimeTypes()
+            throws Exception {
+        Path assembly = serverAssemblyPath();
+
+        for (String variant : GREMLIN_SERVER_CONFIG_VARIANTS) {
+            Settings settings = Settings.read(assembly.resolve(variant)
+                                                   .toString());
+
+            assertSupportsTypedAndUntypedGraphSONMimeTypes(variant,
+                                                           graphSONMimeTypes(settings));
+        }
+    }
+
+    private static void assertSupportsTypedAndUntypedGraphSONMimeTypes(
+            String fileName, Map graphSONMimeTypes) {
+        for (String mimeType : UNTYPED_GRAPHSON_MIME_TYPES) {
+            Assert.assertTrue(fileName + " should support untyped " +
+                              "GraphSON MIME " + mimeType,
+                              graphSONMimeTypes.containsKey(mimeType));
+        }
+        for (String mimeType : TYPED_GRAPHSON_MIME_TYPES) {
+            Assert.assertTrue(fileName + " should support typed GraphSON " +
+                              "MIME " + mimeType,
+                              graphSONMimeTypes.containsKey(mimeType));
+        }
+        Assert.assertEquals(fileName + " should keep application/json " +
+                            "mapped to the untyped V1 serializer",
+                            GRAPHSON_UNTYPED_V1,
+                            graphSONMimeTypes.get("application/json"));
+    }
+
+    @Test
+    public void testConfiguredGraphSONSerializersCanSerializeHugeGraphTypes()
+            throws Exception {
+        Settings settings = readGremlinServerSettings();
+        List typedSerializers = new ArrayList<>();
+        boolean foundUntyped = false;
+
+        for (Settings.SerializerSettings serializerSettings :
+             settings.serializers) {
+            if (!serializerSettings.className.startsWith(SERIALIZER_PACKAGE +
+                                                         "GraphSON")) {
+                continue;
+            }
+
+            MessageTextSerializer serializer =
+                    newTextSerializer(serializerSettings.className);
+            serializer.configure(config(serializerSettings.config),
+                                 Collections.emptyMap());
+            boolean typed = !serializerSettings.className.startsWith(
+                    SERIALIZER_PACKAGE + "GraphSONUntyped");
+            if (typed) {
+                typedSerializers.add(serializerSettings.className);
+            } else {
+                foundUntyped = true;
+            }
+            assertCanSerializeHugeGraphTypes(
+                    serializer,
+                    typed && usesStableGraphSONTypes(
+                            serializerSettings.className));
+        }
+
+        Assert.assertTrue("No untyped GraphSON serializer settings found in " +
+                          GREMLIN_SERVER_CONFIG, foundUntyped);
+        Assert.assertEquals("Configured typed GraphSON serializers should " +
+                            "match the fallback set",
+                            TYPED_FALLBACK_SERIALIZERS, typedSerializers);
+    }
+
+    @Test
+    public void testTypedFallbackSerializersCanRoundTripHugeGraphIds()
+            throws Exception {
+        Map config = graphSONV1Config(
+                readGremlinServerSettings());
+
+        for (String serializer : TYPED_FALLBACK_SERIALIZERS) {
+            MessageTextSerializer textSerializer =
+                    newTextSerializer(serializer);
+
+            textSerializer.configure(config(config), Collections.emptyMap());
+            assertCanRoundTripHugeGraphIds(serializer, textSerializer);
+        }
+    }
+
+    @Test
+    public void testConfiguredGraphBinarySerializersCanRoundTripStandardPredicate()
+            throws Exception {
+        Settings settings = readGremlinServerSettings();
+        boolean found = false;
+
+        for (Settings.SerializerSettings serializerSettings :
+             settings.serializers) {
+            if (!serializerSettings.className.startsWith(SERIALIZER_PACKAGE +
+                                                         "GraphBinary")) {
+                continue;
+            }
+
+            MessageSerializer serializer =
+                    newMessageSerializer(serializerSettings.className);
+            serializer.configure(config(serializerSettings.config),
+                                 Collections.emptyMap());
+            assertCanRoundTripStandardPredicate(serializerSettings.className,
+                                                serializer);
+            found = true;
+        }
+
+        Assert.assertTrue("No GraphBinary serializer settings found in " +
+                          GREMLIN_SERVER_CONFIG, found);
+    }
+
+    @Test
+    public void testRemoteObjectsSerializerCanSerializePathShape()
+            throws Exception {
+        RemoteSerializerSettings settings =
+                readRemoteSerializerSettings(REMOTE_OBJECTS_CONFIG);
+        MessageTextSerializer serializer =
+                newTextSerializer(settings.className);
+
+        serializer.configure(config(settings.config), Collections.emptyMap());
+
+        String json = serializeResponse(serializer, testPath());
+
+        Assert.assertContains("\"labels\"", json);
+        Assert.assertContains("\"objects\"", json);
+        Assert.assertContains("marko", json);
+        Assert.assertContains("lop", json);
+        Assert.assertContains("\"a\"", json);
+        Assert.assertContains("\"b\"", json);
+        Assert.assertContains("\"software\"", json);
+    }
+
+    private static Settings readGremlinServerSettings() throws Exception {
+        return Settings.read(configPath(GREMLIN_SERVER_CONFIG).toString());
+    }
+
+    private static String readConfig(String fileName) throws IOException {
+        return Files.readString(configPath(fileName), StandardCharsets.UTF_8);
+    }
+
+    private static Path configPath(String fileName) {
+        return findConfDir().resolve(fileName);
+    }
+
+    private static Path serverAssemblyPath() {
+        return findConfDir().getParent().getParent();
+    }
+
+    private static Path findConfDir() {
+        String configuredDir = System.getProperty("hugegraph.conf.dir");
+        Path configuredPath = resolveConfiguredDir(configuredDir);
+        if (configuredPath != null) {
+            return configuredPath;
+        }
+
+        String envDir = System.getenv("HUGEGRAPH_CONF_DIR");
+        Path envPath = resolveConfiguredDir(envDir);
+        if (envPath != null) {
+            return envPath;
+        }
+
+        Path userDir = Paths.get(System.getProperty("user.dir"));
+        List candidates = new ArrayList<>();
+
+        Path parent = userDir.getParent();
+        if (parent != null) {
+            candidates.add(parent.resolve("hugegraph-dist")
+                                 .resolve("src")
+                                 .resolve("assembly")
+                                 .resolve("static")
+                                 .resolve("conf"));
+        }
+        candidates.add(userDir.resolve("hugegraph-server")
+                              .resolve("hugegraph-dist")
+                              .resolve("src")
+                              .resolve("assembly")
+                              .resolve("static")
+                              .resolve("conf"));
+
+        for (Path candidate : candidates) {
+            if (Files.isDirectory(candidate)) {
+                return candidate;
+            }
+        }
+
+        Assert.fail(String.format("Can't find hugegraph-dist static conf from" +
+                                  " %s (hugegraph.conf.dir=%s," +
+                                  " HUGEGRAPH_CONF_DIR=%s, candidates=%s)",
+                                  userDir, configuredDir, envDir, candidates));
+        return userDir;
+    }
+
+    private static Path resolveConfiguredDir(String path) {
+        if (path == null || path.isEmpty()) {
+            return null;
+        }
+        Path configured = Paths.get(path);
+        if (Files.isDirectory(configured)) {
+            return configured;
+        }
+        return null;
+    }
+
+    private static void assertUsesHugeGraphIoRegistry(String fileName,
+                                                      String content) {
+        Assert.assertTrue(fileName + " should keep HugeGraphIoRegistry",
+                          content.contains(IO_REGISTRY));
+    }
+
+    private static void assertSerializerClassNamesUseUtilPackage(
+            String fileName, String content) {
+        Assert.assertFalse(content.contains(
+                "org.apache.tinkerpop.gremlin.driver.ser."));
+        Assert.assertFalse(content.contains(
+                "org.apache.tinkerpop.gremlin.server.ser."));
+
+        Matcher matcher = CLASS_NAME.matcher(content);
+        boolean found = false;
+        while (matcher.find()) {
+            found = true;
+            String className = matcher.group(1);
+            Assert.assertTrue(fileName + " has outdated serializer " +
+                              className,
+                              className.startsWith(SERIALIZER_PACKAGE));
+        }
+        Assert.assertTrue("No serializer className found in " + fileName,
+                          found);
+    }
+
+    private static void assertConfiguredSerializerClassesAreLoadable(
+            String fileName, String content) throws ClassNotFoundException {
+        Matcher matcher = CLASS_NAME.matcher(content);
+        boolean found = false;
+        while (matcher.find()) {
+            found = true;
+            Class.forName(matcher.group(1));
+        }
+        Assert.assertTrue("No serializer className found in " + fileName,
+                          found);
+    }
+
+    private static Map graphSONMimeTypes(Settings settings)
+            throws Exception {
+        Map mimeTypes = new HashMap<>();
+
+        for (Settings.SerializerSettings serializerSettings :
+             settings.serializers) {
+            if (!serializerSettings.className.startsWith(SERIALIZER_PACKAGE +
+                                                         "GraphSON")) {
+                continue;
+            }
+
+            MessageSerializer serializer =
+                    newMessageSerializer(serializerSettings.className);
+            for (String mimeType : serializer.mimeTypesSupported()) {
+                mimeTypes.putIfAbsent(mimeType, serializerSettings.className);
+            }
+        }
+
+        return mimeTypes;
+    }
+
+    private static MessageTextSerializer newTextSerializer(String className)
+            throws Exception {
+        MessageSerializer serializer = newMessageSerializer(className);
+
+        Assert.assertTrue(className + " should be a MessageTextSerializer",
+                          serializer instanceof MessageTextSerializer);
+        return (MessageTextSerializer) serializer;
+    }
+
+    private static MessageSerializer newMessageSerializer(String className)
+            throws Exception {
+        Object serializer = Class.forName(className)
+                                 .getDeclaredConstructor()
+                                 .newInstance();
+
+        Assert.assertTrue(className + " should be a MessageSerializer",
+                          serializer instanceof MessageSerializer);
+        return (MessageSerializer) serializer;
+    }
+
+    private static String serializeResponse(MessageTextSerializer serializer,
+                                            Object result)
+            throws Exception {
+        ResponseMessage response = ResponseMessage.build(UUID.randomUUID())
+                                                  .code(ResponseStatusCode.SUCCESS)
+                                                  .result(result)
+                                                  .create();
+
+        return serializer.serializeResponseAsString(response,
+                                                    ByteBufAllocator.DEFAULT);
+    }
+
+    private static ResponseMessage roundTripResponse(
+            MessageTextSerializer serializer, Object result)
+            throws Exception {
+        ResponseMessage response = ResponseMessage.build(UUID.randomUUID())
+                                                  .code(ResponseStatusCode.SUCCESS)
+                                                  .result(result)
+                                                  .create();
+        String json = serializer.serializeResponseAsString(
+                response, ByteBufAllocator.DEFAULT);
+        return serializer.deserializeResponse(json);
+    }
+
+    private static ResponseMessage roundTripBinaryResponse(
+            MessageSerializer serializer, Object result)
+            throws Exception {
+        ResponseMessage response = ResponseMessage.build(UUID.randomUUID())
+                                                  .code(ResponseStatusCode.SUCCESS)
+                                                  .result(result)
+                                                  .create();
+        ByteBuf buffer = serializer.serializeResponseAsBinary(
+                response, ByteBufAllocator.DEFAULT);
+        try {
+            return serializer.deserializeResponse(buffer);
+        } finally {
+            buffer.release();
+        }
+    }
+
+    @SuppressWarnings("unchecked")
+    private static RemoteSerializerSettings readRemoteSerializerSettings(
+            String fileName) throws IOException {
+        try (InputStream input = Files.newInputStream(configPath(fileName))) {
+            Map root = new Yaml().load(input);
+            Map serializer =
+                    (Map) root.get("serializer");
+
+            Assert.assertNotNull("No serializer in " + fileName, serializer);
+            String className = (String) serializer.get("className");
+            Map config =
+                    (Map) serializer.get("config");
+
+            Assert.assertNotNull("No serializer className in " + fileName,
+                                 className);
+            Assert.assertNotNull("No serializer config in " + fileName,
+                                 config);
+            return new RemoteSerializerSettings(className, config);
+        }
+    }
+
+    private static Map graphSONV1Config(Settings settings) {
+        for (Settings.SerializerSettings serializer : settings.serializers) {
+            if (GRAPHSON_UNTYPED_V1.equals(serializer.className)) {
+                Assert.assertNotNull(serializer.config);
+                return serializer.config;
+            }
+        }
+
+        Assert.fail("No " + GRAPHSON_UNTYPED_V1 + " found in " +
+                    GREMLIN_SERVER_CONFIG);
+        return Collections.emptyMap();
+    }
+
+    private static Map config(Map config) {
+        if (config == null) {
+            return Collections.emptyMap();
+        }
+        return new HashMap<>(config);
+    }
+
+    private static org.apache.tinkerpop.gremlin.process.traversal.Path testPath() {
+        return MutablePath.make()
+                          .extend(IdGenerator.of("marko"), Set.of("a"))
+                          .extend(IdGenerator.of("lop"),
+                                  Set.of("b", "software"));
+    }
+
+    private static void assertCanSerializeHugeGraphTypes(
+            MessageTextSerializer serializer, boolean typed)
+            throws Exception {
+        Object id = IdGenerator.of("marko");
+        Object uuidId = IdGenerator.of(
+                UUID.fromString("3cfcafc8-7906-4ab7-a207-4ded056f58de"));
+        Object edgeId = EdgeId.parse("S1>2>3>4>L6");
+        String fileJson = serializeResponse(serializer, new File("test.text"));
+        String idJson = serializeResponse(serializer, id);
+        String uuidJson = serializeResponse(serializer, uuidId);
+        String edgeJson = serializeResponse(serializer, edgeId);
+
+        Assert.assertContains("\"file\"", fileJson);
+        Assert.assertContains("test.text", fileJson);
+        Assert.assertContains("marko", idJson);
+        Assert.assertContains("3cfcafc8-7906-4ab7-a207-4ded056f58de",
+                              uuidJson);
+        Assert.assertContains("S1>2>3>4>L6", edgeJson);
+
+        if (typed) {
+            assertContainsGraphSONType(fileJson, "hugegraph:File");
+            assertContainsGraphSONType(idJson, "hugegraph:StringId");
+            assertContainsGraphSONType(uuidJson, "hugegraph:UuidId");
+            assertContainsGraphSONType(edgeJson, "hugegraph:EdgeId");
+        }
+    }
+
+    private static boolean usesStableGraphSONTypes(String serializer) {
+        // GraphSON V1 uses legacy @class wrapping; assert stable
+        // hugegraph:* @type names for V2/V3 typed fallback serializers.
+        return !serializer.endsWith("GraphSONMessageSerializerV1");
+    }
+
+    private static void assertCanRoundTripHugeGraphIds(
+            String serializerName, MessageTextSerializer serializer)
+            throws Exception {
+        List ids = Arrays.asList(
+                IdGenerator.of("marko"),
+                IdGenerator.of(123L),
+                IdGenerator.of(UUID.fromString(
+                        "3cfcafc8-7906-4ab7-a207-4ded056f58de")),
+                EdgeId.parse("S1>2>3>4>L6")
+        );
+
+        for (Object expected : ids) {
+            ResponseMessage response = roundTripResponse(serializer, expected);
+            Object actual = response.getResult().getData();
+            String message = serializerName + " should round-trip " +
+                             expected.getClass().getSimpleName();
+            Assert.assertEquals(message, expected.getClass(),
+                                actual.getClass());
+            Assert.assertEquals(message, expected, actual);
+        }
+    }
+
+    private static void assertCanRoundTripStandardPredicate(
+            String serializerName, MessageSerializer serializer)
+            throws Exception {
+        P expected = P.eq("marko");
+        ResponseMessage response = roundTripBinaryResponse(serializer,
+                                                           expected);
+        Object actual = response.getResult().getData();
+        String message = serializerName +
+                         " should round-trip a standard predicate";
+        Assert.assertInstanceOf(P.class, actual);
+        Assert.assertEquals(message, expected, actual);
+    }
+
+    private static void assertContainsGraphSONType(String json,
+                                                   String graphSONType) {
+        Assert.assertContains("\"@type\"", json);
+        Assert.assertContains(graphSONType, json);
+    }
+
+    private static final class RemoteSerializerSettings {
+
+        private final String className;
+        private final Map config;
+
+        private RemoteSerializerSettings(String className,
+                                         Map config) {
+            this.className = className;
+            this.config = config;
+        }
+    }
+}
diff --git a/hugegraph-server/hugegraph-test/src/main/java/org/apache/hugegraph/unit/core/ConditionTest.java b/hugegraph-server/hugegraph-test/src/main/java/org/apache/hugegraph/unit/core/ConditionTest.java
index ba4b09dcab..bd376d495d 100644
--- a/hugegraph-server/hugegraph-test/src/main/java/org/apache/hugegraph/unit/core/ConditionTest.java
+++ b/hugegraph-server/hugegraph-test/src/main/java/org/apache/hugegraph/unit/core/ConditionTest.java
@@ -25,8 +25,10 @@
 import org.apache.hugegraph.backend.query.Condition.RelationType;
 import org.apache.hugegraph.backend.query.Condition.SyspropRelation;
 import org.apache.hugegraph.testutil.Assert;
+import org.apache.hugegraph.traversal.optimize.ConditionP;
 import org.apache.hugegraph.type.define.HugeKeys;
 import org.apache.hugegraph.unit.BaseUnitTest;
+import org.apache.tinkerpop.gremlin.process.traversal.PBiPredicate;
 import org.junit.Test;
 
 import com.google.common.collect.ImmutableList;
@@ -149,6 +151,69 @@ public void testConditionEq() {
         Assert.assertFalse(c4.test(new Date(0L)));
     }
 
+    @Test
+    @SuppressWarnings("unchecked")
+    public void testRelationTypeImplementsTinkerPopBiPredicate() {
+        PBiPredicate contains =
+                (PBiPredicate) (Object) RelationType.CONTAINS;
+        Assert.assertEquals("contains", contains.getPredicateName());
+        Assert.assertTrue(contains.test(ImmutableList.of("marko", "josh"),
+                                        "marko"));
+        Assert.assertFalse(contains.test(ImmutableList.of("marko", "josh"),
+                                         "vadas"));
+
+        PBiPredicate containsKey =
+                (PBiPredicate) (Object)
+                RelationType.CONTAINS_KEY;
+        Assert.assertEquals("containsk", containsKey.getPredicateName());
+        Assert.assertTrue(containsKey.test(ImmutableMap.of("name", "marko"),
+                                           "name"));
+
+        PBiPredicate textContains =
+                (PBiPredicate) (Object)
+                RelationType.TEXT_CONTAINS;
+        Assert.assertEquals("textcontains",
+                            textContains.getPredicateName());
+        Assert.assertTrue(textContains.test("marko", "ark"));
+        Assert.assertFalse(textContains.test("marko", "vadas"));
+    }
+
+    @Test
+    public void testConditionPUsesRelationTypeBiPredicate() {
+        ConditionP contains = ConditionP.contains("marko");
+        Assert.assertEquals("contains",
+                            contains.getBiPredicate().getPredicateName());
+        Assert.assertTrue(contains.test(ImmutableList.of("marko", "josh")));
+        Assert.assertFalse(ConditionP.contains("vadas")
+                                     .test(ImmutableList.of("marko", "josh")));
+
+        ConditionP containsKey = ConditionP.containsK("name");
+        Assert.assertEquals("containsk",
+                            containsKey.getBiPredicate().getPredicateName());
+        Assert.assertTrue(containsKey.test(ImmutableMap.of("name", "marko")));
+        Assert.assertFalse(ConditionP.containsK("age")
+                                     .test(ImmutableMap.of("name", "marko")));
+
+        ConditionP containsValue = ConditionP.containsV("marko");
+        Assert.assertEquals("containsv",
+                            containsValue.getBiPredicate().getPredicateName());
+        Assert.assertTrue(containsValue.test(
+                ImmutableMap.of("name", "marko")));
+        Assert.assertFalse(ConditionP.containsV("vadas")
+                                     .test(ImmutableMap.of("name", "marko")));
+
+        ConditionP textContains = ConditionP.textContains("ark");
+        Assert.assertEquals("textcontains",
+                            textContains.getBiPredicate().getPredicateName());
+        Assert.assertTrue(textContains.test("marko"));
+        Assert.assertFalse(ConditionP.textContains("vadas").test("marko"));
+
+        ConditionP eq = ConditionP.eq(new String[]{"a", "b"});
+        Assert.assertEquals("==", eq.getBiPredicate().getPredicateName());
+        Assert.assertTrue(eq.test(new String[]{"a", "b"}));
+        Assert.assertFalse(eq.test(new String[]{"a", "c"}));
+    }
+
     @Test
     public void testConditionGt() {
         Condition c1 = Condition.gt(HugeKeys.ID, 123);
diff --git a/hugegraph-server/hugegraph-test/src/main/java/org/apache/hugegraph/unit/core/GroovyScriptEngineCompatibilityTest.java b/hugegraph-server/hugegraph-test/src/main/java/org/apache/hugegraph/unit/core/GroovyScriptEngineCompatibilityTest.java
new file mode 100644
index 0000000000..3db519db8f
--- /dev/null
+++ b/hugegraph-server/hugegraph-test/src/main/java/org/apache/hugegraph/unit/core/GroovyScriptEngineCompatibilityTest.java
@@ -0,0 +1,49 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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 org.apache.hugegraph.unit.core;
+
+import javax.script.Compilable;
+import javax.script.CompiledScript;
+import javax.script.ScriptEngine;
+import javax.script.ScriptEngineManager;
+import javax.script.ScriptException;
+
+import org.apache.hugegraph.testutil.Assert;
+import org.apache.hugegraph.unit.BaseUnitTest;
+import org.junit.Test;
+
+public class GroovyScriptEngineCompatibilityTest extends BaseUnitTest {
+
+    @Test
+    public void testGroovyJsr223EngineCanCompileAndEvaluate()
+           throws ScriptException {
+        ScriptEngine engine = new ScriptEngineManager().getEngineByName(
+                "groovy");
+
+        Assert.assertNotNull(engine);
+        Assert.assertEquals("org.codehaus.groovy.jsr223." +
+                            "GroovyScriptEngineImpl",
+                            engine.getClass().getName());
+        Assert.assertTrue(engine instanceof Compilable);
+
+        CompiledScript script = ((Compilable) engine).compile(
+                "def add = { a, b -> a + b }; add(2, 3)");
+
+        Assert.assertEquals(5, script.eval());
+    }
+}
diff --git a/hugegraph-server/hugegraph-test/src/main/java/org/apache/hugegraph/unit/core/HugeFeaturesTest.java b/hugegraph-server/hugegraph-test/src/main/java/org/apache/hugegraph/unit/core/HugeFeaturesTest.java
new file mode 100644
index 0000000000..08f12e9fc8
--- /dev/null
+++ b/hugegraph-server/hugegraph-test/src/main/java/org/apache/hugegraph/unit/core/HugeFeaturesTest.java
@@ -0,0 +1,37 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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 org.apache.hugegraph.unit.core;
+
+import org.apache.hugegraph.structure.HugeFeatures;
+import org.junit.Assert;
+import org.junit.Test;
+
+public class HugeFeaturesTest {
+
+    @Test
+    public void testUniformListValueFeatureContract() {
+        HugeFeatures features = new HugeFeatures(null, true);
+
+        Assert.assertTrue(features.graph().variables()
+                                  .supportsUniformListValues());
+        Assert.assertFalse(features.vertex().properties()
+                                   .supportsUniformListValues());
+        Assert.assertFalse(features.edge().properties()
+                                   .supportsUniformListValues());
+    }
+}
diff --git a/hugegraph-server/hugegraph-test/src/main/java/org/apache/hugegraph/unit/serializer/HugeGraphSONModuleTest.java b/hugegraph-server/hugegraph-test/src/main/java/org/apache/hugegraph/unit/serializer/HugeGraphSONModuleTest.java
new file mode 100644
index 0000000000..5b7b5fa4ab
--- /dev/null
+++ b/hugegraph-server/hugegraph-test/src/main/java/org/apache/hugegraph/unit/serializer/HugeGraphSONModuleTest.java
@@ -0,0 +1,148 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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 org.apache.hugegraph.unit.serializer;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.File;
+import java.io.IOException;
+import java.nio.charset.StandardCharsets;
+import java.util.Map;
+import java.util.UUID;
+
+import org.apache.hugegraph.backend.id.EdgeId;
+import org.apache.hugegraph.backend.id.IdGenerator;
+import org.apache.hugegraph.backend.id.IdGenerator.LongId;
+import org.apache.hugegraph.backend.id.IdGenerator.StringId;
+import org.apache.hugegraph.backend.id.IdGenerator.UuidId;
+import org.apache.hugegraph.io.HugeGraphIoRegistry;
+import org.apache.hugegraph.schema.PropertyKey;
+import org.apache.hugegraph.testutil.Assert;
+import org.apache.hugegraph.unit.BaseUnitTest;
+import org.apache.hugegraph.unit.FakeObjects;
+import org.apache.hugegraph.util.JsonUtil;
+import org.apache.tinkerpop.gremlin.structure.io.graphson.GraphSONMapper;
+import org.apache.tinkerpop.gremlin.structure.io.graphson.GraphSONReader;
+import org.apache.tinkerpop.gremlin.structure.io.graphson.GraphSONVersion;
+import org.apache.tinkerpop.gremlin.structure.io.graphson.GraphSONWriter;
+import org.apache.tinkerpop.gremlin.structure.io.graphson.TypeInfo;
+import org.junit.Test;
+
+public class HugeGraphSONModuleTest extends BaseUnitTest {
+
+    @Test
+    public void testSerializeFileWithGraphSONTypeInfo() throws IOException {
+        String json = writeTyped(new File("test.text"));
+        Map typedFile = JsonUtil.fromJson(json, Map.class);
+        Object value = typedFile.get("@value");
+
+        Assert.assertEquals("hugegraph:File", typedFile.get("@type"));
+        Assert.assertInstanceOf(Map.class, value);
+        Assert.assertEquals("test.text", ((Map) value).get("file"));
+        Assert.assertContains("hugegraph:File", json);
+        Assert.assertContains("\"file\"", json);
+        Assert.assertContains("test.text", json);
+
+        File file = readTyped(json, File.class);
+        Assert.assertEquals("test.text", file.getName());
+    }
+
+    @Test
+    public void testRoundTripIdWithGraphSONTypeInfo() throws IOException {
+        StringId expectedStringId = (StringId) IdGenerator.of("marko");
+        LongId expectedLongId = (LongId) IdGenerator.of(123L);
+        UuidId expectedUuidId = (UuidId) IdGenerator.of(
+                UUID.fromString("3cfcafc8-7906-4ab7-a207-4ded056f58de"));
+        EdgeId expectedEdgeId = EdgeId.parse("S1>2>3>4>L6");
+
+        String stringId = writeTyped(expectedStringId);
+        String longId = writeTyped(expectedLongId);
+        String uuidId = writeTyped(expectedUuidId);
+        String edgeId = writeTyped(expectedEdgeId);
+
+        Assert.assertContains("hugegraph:StringId", stringId);
+        Assert.assertContains("marko", stringId);
+        Assert.assertContains("hugegraph:LongId", longId);
+        Assert.assertContains("123", longId);
+        Assert.assertContains("hugegraph:UuidId", uuidId);
+        Assert.assertContains("3cfcafc8-7906-4ab7-a207-4ded056f58de",
+                              uuidId);
+        Assert.assertContains("hugegraph:EdgeId", edgeId);
+        Assert.assertContains("S1>2>3>4>L6", edgeId);
+
+        Assert.assertEquals(expectedStringId,
+                            readTyped(stringId, StringId.class));
+        Assert.assertEquals(expectedLongId,
+                            readTyped(longId, LongId.class));
+        Assert.assertEquals(expectedUuidId,
+                            readTyped(uuidId, UuidId.class));
+        Assert.assertEquals(expectedEdgeId,
+                            readTyped(edgeId, EdgeId.class));
+    }
+
+    @Test
+    public void testSerializeSchemaWithUntypedGraphSONModule() throws IOException {
+        FakeObjects objects = new FakeObjects();
+        PropertyKey propertyKey = objects.newPropertyKey(IdGenerator.of(1L),
+                                                         "name");
+
+        String json = writeUntyped(propertyKey);
+
+        Assert.assertContains("\"name\"", json);
+    }
+
+    private static String writeTyped(Object object) throws IOException {
+        GraphSONMapper mapper = mapper(TypeInfo.PARTIAL_TYPES);
+        GraphSONWriter writer = GraphSONWriter.build().mapper(mapper).create();
+        ByteArrayOutputStream output = new ByteArrayOutputStream();
+
+        writer.writeObject(output, object);
+
+        return output.toString(StandardCharsets.UTF_8.name());
+    }
+
+    private static  T readTyped(String json, Class clazz)
+            throws IOException {
+        GraphSONMapper mapper = mapper(TypeInfo.PARTIAL_TYPES);
+        GraphSONReader reader = GraphSONReader.build().mapper(mapper).create();
+        ByteArrayInputStream input = new ByteArrayInputStream(
+                json.getBytes(StandardCharsets.UTF_8));
+
+        return reader.readObject(input, clazz);
+    }
+
+    private static String writeUntyped(Object object) throws IOException {
+        GraphSONMapper mapper = mapper(TypeInfo.NO_TYPES);
+        GraphSONWriter writer = GraphSONWriter.build().mapper(mapper).create();
+        ByteArrayOutputStream output = new ByteArrayOutputStream();
+
+        writer.writeObject(output, object);
+
+        return output.toString(StandardCharsets.UTF_8.name());
+    }
+
+    private static GraphSONMapper mapper(TypeInfo typeInfo) {
+        GraphSONMapper mapper = GraphSONMapper.build()
+                                              .version(GraphSONVersion.V3_0)
+                                              .typeInfo(typeInfo)
+                                              .addRegistry(HugeGraphIoRegistry.instance())
+                                              .create();
+
+        return mapper;
+    }
+}
diff --git a/hugegraph-server/hugegraph-test/src/main/resources/fast-methods.filter b/hugegraph-server/hugegraph-test/src/main/resources/fast-methods.filter
index 2d162bd705..82fb620d00 100644
--- a/hugegraph-server/hugegraph-test/src/main/resources/fast-methods.filter
+++ b/hugegraph-server/hugegraph-test/src/main/resources/fast-methods.filter
@@ -17,40 +17,38 @@
 #
 
 ####################    structure suite    ####################
-
-## automatic ID for edges
+## Filter reason categories:
+## HG-LEGACY: Existing HugeGraph feature boundary before this upgrade
+## TP37-SEMANTICS: New or renamed TinkerPop 3.7 suite expectation
+## TEST-INFRA: Test harness/storage lifecycle issue, not a feature regression
+## PERF: Fast-suite-only runtime guard
+## FOLLOW-UP: Keep tracked for later behavior or infrastructure cleanup
+
+## HG-LEGACY: automatic ID for edges
 org.apache.tinkerpop.gremlin.structure.util.detached.DetachedEdgeTest.shouldNotEvaluateToEqualDifferentId: Not support automatic/custom id for edge
 org.apache.tinkerpop.gremlin.structure.util.reference.ReferenceEdgeTest.shouldNotEvaluateToEqualDifferentId: Not support automatic/custom id for edge
 org.apache.tinkerpop.gremlin.structure.EdgeTest.BasicEdgeTest.shouldValidateIdEquality: Not support automatic/custom id for edge
 org.apache.tinkerpop.gremlin.structure.EdgeTest.BasicEdgeTest.shouldValidateEquality: Not support automatic/custom id for edge
 
-## property ID
+## HG-LEGACY: property ID
 org.apache.tinkerpop.gremlin.structure.util.detached.DetachedPropertyTest.shouldNotBeEqualPropertiesAsThereIsDifferentKey: Not support property id
 
-## id should be String type
+## HG-LEGACY: id should be String type
 org.apache.tinkerpop.gremlin.structure.GraphTest.shouldAddVertexWithUserSuppliedStringId: expect vertex.id is String type
 
-## same id for element
+## HG-LEGACY: same id for element
 org.apache.tinkerpop.gremlin.structure.GraphTest.shouldHaveExceptionConsistencyWhenAssigningSameIdOnVertex: Assigning the same ID to an Element is accepted(override), but tinkerpop expect throw an exception
 
-## uniform list
-org.apache.tinkerpop.gremlin.structure.PropertyTest.PropertyFeatureSupportTest.shouldSetValueOnVertexOnAdd[supportsUniformListValues([100, 200, 300])]: Not support uniform of Integer because define uniform as String list
-org.apache.tinkerpop.gremlin.structure.PropertyTest.PropertyFeatureSupportTest.shouldSetValueOnEdgeOnAdd[supportsUniformListValues([100, 200, 300])]: Not support uniform of Integer because define uniform as String list
-org.apache.tinkerpop.gremlin.structure.PropertyTest.PropertyFeatureSupportTest.shouldSetValueOnVertex[supportsUniformListValues([100, 200, 300])]: Not support uniform of Integer because define uniform as String list
-org.apache.tinkerpop.gremlin.structure.PropertyTest.PropertyFeatureSupportTest.shouldSetValueOnEdge[supportsUniformListValues([100, 200, 300])]: Not support uniform of Integer because define uniform as String list
-org.apache.tinkerpop.gremlin.structure.PropertyTest.PropertyFeatureSupportTest.shouldSetValueOnVertexOnAdd[supportsUniformListValues([try1, try2])]: Uniform list can't be accessed by vertex.property() which is supposed right by tinkerpop
-org.apache.tinkerpop.gremlin.structure.PropertyTest.PropertyFeatureSupportTest.shouldSetValueOnVertex[supportsUniformListValues([try1, try2])]: Uniform list can't be accessed by vertex.property() which is supposed right by tinkerpop
-
-# expect multi properties after setting single property multi times
+## HG-LEGACY: expect multi properties after setting single property multi times
 org.apache.tinkerpop.gremlin.structure.GraphTest.shouldOverwriteEarlierKeyValuesWithLaterKeyValuesOnAddVertexIfMultiProperty: Expect multi properties after setting single property multi times
 org.apache.tinkerpop.gremlin.structure.VertexPropertyTest.VertexPropertyRemoval.shouldRemoveMultiProperties: Expect multi properties after setting single property multi times
 org.apache.tinkerpop.gremlin.structure.VertexPropertyTest.VertexPropertyRemoval.shouldRemoveMultiPropertiesWhenVerticesAreRemoved: Expect multi properties after setting single property multi times
 org.apache.tinkerpop.gremlin.structure.VertexPropertyTest.VertexPropertyAddition.shouldAllowIdenticalValuedMultiProperties: Expect multi properties after setting single property multi times
 
-# not support nested property
+## HG-LEGACY: not support nested property
 org.apache.tinkerpop.gremlin.structure.VertexPropertyTest.VertexPropertyRemoval.shouldAllowIteratingAndRemovingVertexPropertyProperties: Not support nested property
 
-## failed tests with wrong edge number, reason: replicated edges treated as one
+## HG-LEGACY: failed tests with wrong edge number, reason: replicated edges treated as one
 org.apache.tinkerpop.gremlin.algorithm.generator.DistributionGeneratorTest.ProcessorTest.shouldProcessEdges: Replicated edges treated as one
 org.apache.tinkerpop.gremlin.algorithm.generator.DistributionGeneratorTest.DifferentDistributionsTest.shouldGenerateSameGraph[test(NormalDistribution{stdDeviation=2.0, mean=0.0},NormalDistribution{stdDeviation=2.0, mean=0.0})]: Replicated edges treated as one
 org.apache.tinkerpop.gremlin.algorithm.generator.DistributionGeneratorTest.DifferentDistributionsTest.shouldGenerateDifferentGraph[test(NormalDistribution{stdDeviation=2.0, mean=0.0},NormalDistribution{stdDeviation=2.0, mean=0.0})]: Replicated edges treated as one
@@ -65,27 +63,54 @@ org.apache.tinkerpop.gremlin.algorithm.generator.DistributionGeneratorTest.Diffe
 org.apache.tinkerpop.gremlin.algorithm.generator.DistributionGeneratorTest.DifferentDistributionsTest.shouldGenerateSameGraph[test(PowerLawDistribution{gamma=2.3, multiplier=0.0},PowerLawDistribution{gamma=2.8, multiplier=0.0})]: Replicated edges treated as one
 org.apache.tinkerpop.gremlin.algorithm.generator.DistributionGeneratorTest.DifferentDistributionsTest.shouldGenerateDifferentGraph[test(PowerLawDistribution{gamma=2.3, multiplier=0.0},PowerLawDistribution{gamma=2.8, multiplier=0.0})]: Replicated edges treated as one
 
-## user supplied numeric id
+## HG-LEGACY: user supplied numeric id
 org.apache.tinkerpop.gremlin.structure.FeatureSupportTest.VertexFunctionalityTest.shouldSupportUserSuppliedIdsOfTypeNumericLong: Not open the feature 'support user supplied id of numeric long' although numeric long id is allowed
 org.apache.tinkerpop.gremlin.structure.FeatureSupportTest.VertexFunctionalityTest.shouldSupportUserSuppliedIdsOfTypeNumericInt: Not open the feature 'support user supplied id of numeric long' although numeric long id is allowed
 
-## user supplied uuid id
+## HG-LEGACY/FOLLOW-UP: user supplied uuid id feature flag remains closed
 org.apache.tinkerpop.gremlin.structure.FeatureSupportTest.VertexFunctionalityTest.shouldSupportUserSuppliedIdsOfTypeUuid: Not open the feature 'support user supplied id of uuid' although uuid id is allowed
 
-## edge id format validate firstly and throw NotFoundException if invalid
+## HG-LEGACY/FOLLOW-UP: edge id format validation throws NotFoundException first
 org.apache.tinkerpop.gremlin.structure.GraphTest.shouldHaveExceptionConsistencyWhenFindEdgeByIdThatIsNonExistentViaIterator: Invalid format of edge id will introduce throwing NotFoundException before try to query in backend store
 
-## not bugs
+## HG-LEGACY/TEST-INFRA/FOLLOW-UP: existing non-bug behavior and long-running transaction case
 org.apache.tinkerpop.gremlin.structure.GraphTest.shouldRemoveVertices: Random UUID as edge label
 org.apache.tinkerpop.gremlin.structure.GraphTest.shouldRemoveEdges: Random UUID as edge label
 org.apache.tinkerpop.gremlin.structure.TransactionTest.shouldExecuteWithCompetingThreads: Hang the tests because property 'test' have both String and long value
 
-## vertex properties doesn't have nested structures with HugeVertexSerializer
+## TEST-INFRA/FOLLOW-UP: gryo-v3 migration tests reopen source graph path before RocksDB releases lock
+org.apache.tinkerpop.gremlin.structure.io.IoGraphTest.shouldMigrateModernGraph[gryo-v3]: RocksDB lock not released between source and target graph in gryo-v3 migration test
+org.apache.tinkerpop.gremlin.structure.io.IoGraphTest.shouldMigrateClassicGraph[gryo-v3]: RocksDB lock not released between source and target graph in gryo-v3 migration test
+
+## TP37-SEMANTICS/FOLLOW-UP: TinkerPop 3.7 transaction lifecycle checks spawned thread transactions
+org.apache.tinkerpop.gremlin.structure.TransactionTest.shouldExecuteCompetingThreadsOnMultipleDbInstances: HugeGraph uses ThreadLocal transactions, spawned thread tx not auto-closed on graph close
+org.apache.tinkerpop.gremlin.structure.TransactionTest.shouldAllowReferenceOfEdgeIdOutsideOfOriginalThreadManual: HugeGraph uses ThreadLocal transactions, spawned thread tx not auto-closed on graph close
+org.apache.tinkerpop.gremlin.structure.TransactionTest.shouldSupportTransactionIsolationCommitCheck: HugeGraph uses ThreadLocal transactions, spawned thread tx not auto-closed on graph close
+org.apache.tinkerpop.gremlin.structure.TransactionTest.shouldAllowReferenceOfVertexIdOutsideOfOriginalThreadManual: HugeGraph uses ThreadLocal transactions, spawned thread tx not auto-closed on graph close
+
+## HG-LEGACY/TP37-SEMANTICS/FOLLOW-UP: TransactionMultiThreadedTest assumes spawned transaction isolation and cleanup
+org.apache.tinkerpop.gremlin.structure.TransactionMultiThreadedTest.shouldCommit: Memory backend exposes uncommitted vertices across ThreadLocal transactions and spawned tx stays open
+org.apache.tinkerpop.gremlin.structure.TransactionMultiThreadedTest.shouldDeleteVertexOnCommit: Spawned read transaction is not auto-closed after thread exit
+org.apache.tinkerpop.gremlin.structure.TransactionMultiThreadedTest.shouldDeleteRelatedEdgesOnVertexDelete: Spawned verification transaction is not auto-closed after thread exit
+org.apache.tinkerpop.gremlin.structure.TransactionMultiThreadedTest.shouldRollbackAddedVertex: Memory backend exposes uncommitted vertices and spawned tx stays open
+org.apache.tinkerpop.gremlin.structure.TransactionMultiThreadedTest.shouldCommitEdge: Spawned read transactions are not auto-closed after thread exit
+org.apache.tinkerpop.gremlin.structure.TransactionMultiThreadedTest.shouldRollbackAddedEdge: Spawned read transactions are not auto-closed after thread exit
+
+## HG-LEGACY/TP37-SEMANTICS/FOLLOW-UP: MergeEdgeTest hard-codes numeric vertex ids although HugeGraph does not advertise numeric id support
+org.apache.tinkerpop.gremlin.process.traversal.step.map.MergeEdgeTest.Traversals.g_mergeEXlabel_knows_out_marko_in_vadasX: Test requires raw numeric vertex ids while HugeGraph exposes custom Id values
+org.apache.tinkerpop.gremlin.process.traversal.step.map.MergeEdgeTest.Traversals.g_mergeEXlabel_knows_out_marko_in_vadas_weight_05X_exists: Test requires raw numeric vertex ids while HugeGraph exposes custom Id values
+org.apache.tinkerpop.gremlin.process.traversal.step.map.MergeEdgeTest.Traversals.g_mergeEXlabel_knows_out_marko_in_vadasX_optionXonCreate_created_YX_optionXonMatch_created_NX_exists: Test requires raw numeric vertex ids while HugeGraph exposes custom Id values
+org.apache.tinkerpop.gremlin.process.traversal.step.map.MergeEdgeTest.Traversals.g_mergeEXlabel_knows_out_marko_in_vadasX_optionXonCreate_created_YX_optionXonMatch_created_NX_exists_updated: Test requires raw numeric vertex ids while HugeGraph exposes custom Id values
+org.apache.tinkerpop.gremlin.process.traversal.step.map.MergeEdgeTest.Traversals.g_V_hasXperson_name_marko_X_mergeEXlabel_knowsX_optionXonCreate_created_YX_optionXonMatch_created_NX_exists_updated: Test requires raw numeric vertex ids while HugeGraph exposes custom Id values
+org.apache.tinkerpop.gremlin.process.traversal.step.map.MergeEdgeTest.Traversals.g_injectXlabel_knows_out_marko_in_vadasX_mergeE: Test requires raw numeric vertex ids while HugeGraph exposes custom Id values
+org.apache.tinkerpop.gremlin.process.traversal.step.map.MergeEdgeTest.Traversals.g_mergeE_with_outV_inV_options: Test requires raw numeric vertex ids while HugeGraph exposes custom Id values
+
+## HG-LEGACY/FOLLOW-UP: vertex properties don't have nested structures with HugeVertexSerializer
 org.apache.tinkerpop.gremlin.structure.SerializationTest.GraphSONTest.shouldSerializeVertex: Vertex properties doesn't have nested structures with HugeVertexSerializer
 org.apache.tinkerpop.gremlin.structure.SerializationTest.GraphSONTest.shouldSerializePath: Vertex properties doesn't have nested structures with HugeVertexSerializer
 org.apache.tinkerpop.gremlin.structure.SerializationTest.GraphSONTest.shouldSerializeTree: Vertex properties doesn't have nested structures with HugeVertexSerializer
 
-## update property but no commit, lead there are changes in indexTx, can't do index query
+## HG-LEGACY: update property but no commit leaves changes in indexTx
 org.apache.tinkerpop.gremlin.structure.util.detached.DetachedEdgeTest.shouldConstructDetachedEdge: Can't do index query when there are changes in transaction
 org.apache.tinkerpop.gremlin.structure.util.detached.DetachedEdgeTest.shouldConstructDetachedEdgeAsReference: Can't do index query when there are changes in transaction
 org.apache.tinkerpop.gremlin.structure.util.reference.ReferenceEdgeTest.shouldConstructReferenceEdge: Can't do index query when there are changes in transaction
@@ -95,9 +120,12 @@ org.apache.tinkerpop.gremlin.structure.util.reference.ReferenceEdgeTest.shouldCo
 # unsupported automatic edge id, therefore number of edge is wrong
 org.apache.tinkerpop.gremlin.process.traversal.strategy.decoration.EventStrategyProcessTest.shouldTriggerAddEdge: Unsupported automatic edge id, therefore number of edge is wrong
 org.apache.tinkerpop.gremlin.process.traversal.strategy.decoration.EventStrategyProcessTest.shouldTriggerAddEdgeByPath: Unsupported automatic edge id, therefore number of edge is wrong
+org.apache.tinkerpop.gremlin.process.traversal.strategy.decoration.EventStrategyProcessTest.shouldTriggerAddEdgeViaMergeE: Unsupported automatic edge id/event tracking for mergeE
+org.apache.tinkerpop.gremlin.process.traversal.strategy.decoration.EventStrategyProcessTest.shouldTriggerAddVertexWithPropertyThenPropertyAdded: Unsupported vertex property changed event tracking for addV().property()
 # shouldWriteToMultiplePartitions
 org.apache.tinkerpop.gremlin.process.traversal.strategy.decoration.PartitionStrategyProcessTest.shouldWriteToMultiplePartitions: It's not allowed to query by index when there are uncommitted records
 org.apache.tinkerpop.gremlin.process.traversal.strategy.decoration.PartitionStrategyProcessTest.shouldWriteVerticesToMultiplePartitions: It's not allowed to query by index when there are uncommitted records
+org.apache.tinkerpop.gremlin.process.traversal.strategy.decoration.PartitionStrategyProcessTest.shouldPartitionWithAbstractLambdaChildTraversal: Unsupported partition test schema label
 
 # assert error, long time, reason: replicated edges treated as one
 org.apache.tinkerpop.gremlin.process.traversal.step.map.CountTest.Traversals.g_V_both_both_count: Replicated edges treated as one
@@ -118,7 +146,7 @@ org.apache.tinkerpop.gremlin.process.traversal.step.map.AddVertexTest.Traversals
 org.apache.tinkerpop.gremlin.process.traversal.step.map.AddVertexTest.Traversals.g_addVXanimalX_propertyXname_mateoX_propertyXname_gateoX_propertyXname_cateoX_propertyXage_5X: Expect multi properties after setting single property multi times
 org.apache.tinkerpop.gremlin.process.traversal.step.map.AddVertexTest.Traversals.g_V_addVXanimalX_propertyXname_valuesXnameXX_propertyXname_an_animalX_propertyXvaluesXnameX_labelX: Expect multi properties after setting single property multi times
 
-# pass, long time
+## PERF: pass but too slow for fast TinkerPop profile
 org.apache.tinkerpop.gremlin.process.traversal.step.map.CountTest.Traversals.g_V_repeatXoutX_timesX5X_asXaX_outXwrittenByX_asXbX_selectXa_bX_count: long time
 org.apache.tinkerpop.gremlin.process.traversal.step.map.MatchTest.CountMatchTraversals.g_V_matchXa_followedBy_count_isXgtX10XX_b__a_0followedBy_count_isXgtX10XX_bX_count: long time
 org.apache.tinkerpop.gremlin.process.traversal.step.map.MatchTest.CountMatchTraversals.g_V_hasLabelXsongsX_matchXa_name_b__a_performances_cX_selectXb_cX_count: long time
@@ -154,7 +182,7 @@ org.apache.tinkerpop.gremlin.process.traversal.step.ComplexTest.Traversals.class
 org.apache.tinkerpop.gremlin.process.traversal.step.ComplexTest.Traversals.playlistPaths: long time
 org.apache.tinkerpop.gremlin.process.traversal.step.sideEffect.TreeTest.Traversals.g_V_out_out_treeXaX_capXaX: long time
 
-# Unsupported query
+## HG-LEGACY/TP37-SEMANTICS/FOLLOW-UP: unsupported has predicates and empty-id semantics
 org.apache.tinkerpop.gremlin.process.traversal.step.filter.HasTest.Traversals.g_V_hasIdXwithoutXemptyXX_count: Unsupported query 'NOT IN []'
 org.apache.tinkerpop.gremlin.process.traversal.step.filter.HasTest.Traversals.g_V_hasIdXemptyX_count: Unsupported query 'hasId(EmptyList)'
 
@@ -163,13 +191,20 @@ org.apache.tinkerpop.gremlin.process.traversal.step.filter.HasTest.Traversals.g_
 org.apache.tinkerpop.gremlin.process.traversal.step.filter.HasTest.Traversals.g_V_hasXname_startingWithXmarXX: Unsupported predicate 'startingWith(mar)'
 org.apache.tinkerpop.gremlin.process.traversal.step.filter.HasTest.Traversals.g_V_hasXage_withoutX27X_count: Unsupported relation 'NEQ'
 org.apache.tinkerpop.gremlin.process.traversal.step.filter.HasTest.Traversals.g_V_hasXperson_name_containingXoX_andXltXmXXX: Unsupported predicate 'containing(o)'
+org.apache.tinkerpop.gremlin.process.traversal.step.filter.HasTest.Traversals.g_V_hasXname_regexXrMarXX: Unsupported predicate 'regex(^mar)'
+org.apache.tinkerpop.gremlin.process.traversal.step.filter.HasTest.Traversals.g_V_hasXname_regexXTinkerXX: Unsupported predicate 'regex(Tinker)'
+org.apache.tinkerpop.gremlin.process.traversal.step.filter.HasTest.Traversals.g_V_hasXname_regexXTinkerUnicodeXX: Unsupported predicate 'regex(Tinker.*)'
 org.apache.tinkerpop.gremlin.process.traversal.step.filter.HasTest.Traversals.g_V_hasXname_not_startingWithXmarXX: Unsupported predicate 'notStartingWith(mar)'
 org.apache.tinkerpop.gremlin.process.traversal.step.filter.HasTest.Traversals.g_V_hasXname_not_containingXarkXX: Unsupported predicate 'notContaining(ark)'
 org.apache.tinkerpop.gremlin.process.traversal.step.filter.HasTest.Traversals.g_V_hasXname_not_endingWithXasXX: Unsupported predicate 'notEndingWith(as)'
+org.apache.tinkerpop.gremlin.process.traversal.step.filter.HasTest.Traversals.g_V_hasXname_notRegexXrMarXX: Unsupported predicate 'notRegex(^mar)'
 org.apache.tinkerpop.gremlin.process.traversal.step.filter.HasTest.Traversals.g_V_hasXage_withoutX27_29X_count: Unsupported relation 'NEQ'
 org.apache.tinkerpop.gremlin.process.traversal.step.filter.HasTest.Traversals.g_V_hasXname_gtXmX_andXcontainingXoXXX: Unsupported predicate 'containing(o)'
 org.apache.tinkerpop.gremlin.process.traversal.step.filter.HasTest.Traversals.g_V_hasXp_neqXvXX: Don't accept query based on properties [p] that are not indexed in any label, may not match not-equal condition
 
+org.apache.tinkerpop.gremlin.process.traversal.strategy.decoration.ElementIdStrategyProcessTest.shouldSetIdOnAddEWithNamePropertyKeySpecifiedAndNameSuppliedAsProperty: Unsupported ElementIdStrategy edge id/property index query
+org.apache.tinkerpop.gremlin.process.traversal.strategy.decoration.ElementIdStrategyProcessTest.shouldGenerateDefaultIdOnGraphAddVWithGeneratedCustomId: Unsupported ElementIdStrategy generated custom id schema
+
 # Unsupport edge label 'created': 'software' -> 'person', has existed an edgelabel (created: person -> software) in this case
 org.apache.tinkerpop.gremlin.process.traversal.step.map.AddEdgeTest.Traversals.g_V_hasXname_markoX_asXaX_outEXcreatedX_asXbX_inV_addEXselectXbX_labelX_toXaX: Unsupport edge from inV to outV
 
diff --git a/hugegraph-server/hugegraph-test/src/main/resources/methods.filter b/hugegraph-server/hugegraph-test/src/main/resources/methods.filter
index d4b7c3e787..193eaf05ab 100644
--- a/hugegraph-server/hugegraph-test/src/main/resources/methods.filter
+++ b/hugegraph-server/hugegraph-test/src/main/resources/methods.filter
@@ -16,40 +16,37 @@
 # limitations under the License.
 #
 ####################    structure suite    ####################
+## Filter reason categories:
+## HG-LEGACY: Existing HugeGraph feature boundary before this upgrade
+## TP37-SEMANTICS: New or renamed TinkerPop 3.7 suite expectation
+## TEST-INFRA: Test harness/storage lifecycle issue, not a feature regression
+## FOLLOW-UP: Keep tracked for later behavior or infrastructure cleanup
 
-## automatic ID for edges
+## HG-LEGACY: automatic ID for edges
 org.apache.tinkerpop.gremlin.structure.util.detached.DetachedEdgeTest.shouldNotEvaluateToEqualDifferentId: Not support automatic/custom id for edge
 org.apache.tinkerpop.gremlin.structure.util.reference.ReferenceEdgeTest.shouldNotEvaluateToEqualDifferentId: Not support automatic/custom id for edge
 org.apache.tinkerpop.gremlin.structure.EdgeTest.BasicEdgeTest.shouldValidateIdEquality: Not support automatic/custom id for edge
 org.apache.tinkerpop.gremlin.structure.EdgeTest.BasicEdgeTest.shouldValidateEquality: Not support automatic/custom id for edge
 
-## property ID
+## HG-LEGACY: property ID
 org.apache.tinkerpop.gremlin.structure.util.detached.DetachedPropertyTest.shouldNotBeEqualPropertiesAsThereIsDifferentKey: Not support property id
 
-## id should be String type
+## HG-LEGACY: id should be String type
 org.apache.tinkerpop.gremlin.structure.GraphTest.shouldAddVertexWithUserSuppliedStringId: expect vertex.id is String type
 
-## same id for element
+## HG-LEGACY: same id for element
 org.apache.tinkerpop.gremlin.structure.GraphTest.shouldHaveExceptionConsistencyWhenAssigningSameIdOnVertex: Assigning the same ID to an Element is accepted(override), but tinkerpop expect throw an exception
 
-## uniform list
-org.apache.tinkerpop.gremlin.structure.PropertyTest.PropertyFeatureSupportTest.shouldSetValueOnVertexOnAdd[supportsUniformListValues([100, 200, 300])]: Not support uniform of Integer because define uniform as String list
-org.apache.tinkerpop.gremlin.structure.PropertyTest.PropertyFeatureSupportTest.shouldSetValueOnEdgeOnAdd[supportsUniformListValues([100, 200, 300])]: Not support uniform of Integer because define uniform as String list
-org.apache.tinkerpop.gremlin.structure.PropertyTest.PropertyFeatureSupportTest.shouldSetValueOnVertex[supportsUniformListValues([100, 200, 300])]: Not support uniform of Integer because define uniform as String list
-org.apache.tinkerpop.gremlin.structure.PropertyTest.PropertyFeatureSupportTest.shouldSetValueOnEdge[supportsUniformListValues([100, 200, 300])]: Not support uniform of Integer because define uniform as String list
-org.apache.tinkerpop.gremlin.structure.PropertyTest.PropertyFeatureSupportTest.shouldSetValueOnVertexOnAdd[supportsUniformListValues([try1, try2])]: Uniform list can't be accessed by vertex.property() which is supposed right by tinkerpop
-org.apache.tinkerpop.gremlin.structure.PropertyTest.PropertyFeatureSupportTest.shouldSetValueOnVertex[supportsUniformListValues([try1, try2])]: Uniform list can't be accessed by vertex.property() which is supposed right by tinkerpop
-
-# expect multi properties after setting single property multi times
+## HG-LEGACY: expect multi properties after setting single property multi times
 org.apache.tinkerpop.gremlin.structure.GraphTest.shouldOverwriteEarlierKeyValuesWithLaterKeyValuesOnAddVertexIfMultiProperty: Expect multi properties after setting single property multi times
 org.apache.tinkerpop.gremlin.structure.VertexPropertyTest.VertexPropertyRemoval.shouldRemoveMultiProperties: Expect multi properties after setting single property multi times
 org.apache.tinkerpop.gremlin.structure.VertexPropertyTest.VertexPropertyRemoval.shouldRemoveMultiPropertiesWhenVerticesAreRemoved: Expect multi properties after setting single property multi times
 org.apache.tinkerpop.gremlin.structure.VertexPropertyTest.VertexPropertyAddition.shouldAllowIdenticalValuedMultiProperties: Expect multi properties after setting single property multi times
 
-# not support nested property
+## HG-LEGACY: not support nested property
 org.apache.tinkerpop.gremlin.structure.VertexPropertyTest.VertexPropertyRemoval.shouldAllowIteratingAndRemovingVertexPropertyProperties: Not support nested property
 
-## failed tests with wrong edge number, reason: replicated edges treated as one
+## HG-LEGACY: failed tests with wrong edge number, reason: replicated edges treated as one
 org.apache.tinkerpop.gremlin.algorithm.generator.DistributionGeneratorTest.ProcessorTest.shouldProcessEdges: Replicated edges treated as one
 org.apache.tinkerpop.gremlin.algorithm.generator.DistributionGeneratorTest.DifferentDistributionsTest.shouldGenerateSameGraph[test(NormalDistribution{stdDeviation=2.0, mean=0.0},NormalDistribution{stdDeviation=2.0, mean=0.0})]: Replicated edges treated as one
 org.apache.tinkerpop.gremlin.algorithm.generator.DistributionGeneratorTest.DifferentDistributionsTest.shouldGenerateDifferentGraph[test(NormalDistribution{stdDeviation=2.0, mean=0.0},NormalDistribution{stdDeviation=2.0, mean=0.0})]: Replicated edges treated as one
@@ -64,27 +61,54 @@ org.apache.tinkerpop.gremlin.algorithm.generator.DistributionGeneratorTest.Diffe
 org.apache.tinkerpop.gremlin.algorithm.generator.DistributionGeneratorTest.DifferentDistributionsTest.shouldGenerateSameGraph[test(PowerLawDistribution{gamma=2.3, multiplier=0.0},PowerLawDistribution{gamma=2.8, multiplier=0.0})]: Replicated edges treated as one
 org.apache.tinkerpop.gremlin.algorithm.generator.DistributionGeneratorTest.DifferentDistributionsTest.shouldGenerateDifferentGraph[test(PowerLawDistribution{gamma=2.3, multiplier=0.0},PowerLawDistribution{gamma=2.8, multiplier=0.0})]: Replicated edges treated as one
 
-## user supplied numeric id
+## HG-LEGACY: user supplied numeric id
 org.apache.tinkerpop.gremlin.structure.FeatureSupportTest.VertexFunctionalityTest.shouldSupportUserSuppliedIdsOfTypeNumericLong: Not open the feature 'support user supplied id of numeric long' although numeric long id is allowed
 org.apache.tinkerpop.gremlin.structure.FeatureSupportTest.VertexFunctionalityTest.shouldSupportUserSuppliedIdsOfTypeNumericInt: Not open the feature 'support user supplied id of numeric long' although numeric long id is allowed
 
-## user supplied uuid id
+## HG-LEGACY/FOLLOW-UP: user supplied uuid id feature flag remains closed
 org.apache.tinkerpop.gremlin.structure.FeatureSupportTest.VertexFunctionalityTest.shouldSupportUserSuppliedIdsOfTypeUuid: Not open the feature 'support user supplied id of uuid' although uuid id is allowed
 
-## edge id format validate firstly and throw NotFoundException if invalid
+## HG-LEGACY/FOLLOW-UP: edge id format validation throws NotFoundException first
 org.apache.tinkerpop.gremlin.structure.GraphTest.shouldHaveExceptionConsistencyWhenFindEdgeByIdThatIsNonExistentViaIterator: Invalid format of edge id will introduce throwing NotFoundException before try to query in backend store
 
-## not bugs
+## HG-LEGACY/TEST-INFRA/FOLLOW-UP: existing non-bug behavior and long-running transaction case
 org.apache.tinkerpop.gremlin.structure.GraphTest.shouldRemoveVertices: Random UUID as edge label
 org.apache.tinkerpop.gremlin.structure.GraphTest.shouldRemoveEdges: Random UUID as edge label
 org.apache.tinkerpop.gremlin.structure.TransactionTest.shouldExecuteWithCompetingThreads: Hang the tests because property 'test' have both String and long value
 
-## vertex properties doesn't have nested structures with HugeVertexSerializer
+## TEST-INFRA/FOLLOW-UP: gryo-v3 migration tests reopen source graph path before RocksDB releases lock
+org.apache.tinkerpop.gremlin.structure.io.IoGraphTest.shouldMigrateModernGraph[gryo-v3]: RocksDB lock not released between source and target graph in gryo-v3 migration test
+org.apache.tinkerpop.gremlin.structure.io.IoGraphTest.shouldMigrateClassicGraph[gryo-v3]: RocksDB lock not released between source and target graph in gryo-v3 migration test
+
+## TP37-SEMANTICS/FOLLOW-UP: TinkerPop 3.7 transaction lifecycle checks spawned thread transactions
+org.apache.tinkerpop.gremlin.structure.TransactionTest.shouldExecuteCompetingThreadsOnMultipleDbInstances: HugeGraph uses ThreadLocal transactions, spawned thread tx not auto-closed on graph close
+org.apache.tinkerpop.gremlin.structure.TransactionTest.shouldAllowReferenceOfEdgeIdOutsideOfOriginalThreadManual: HugeGraph uses ThreadLocal transactions, spawned thread tx not auto-closed on graph close
+org.apache.tinkerpop.gremlin.structure.TransactionTest.shouldSupportTransactionIsolationCommitCheck: HugeGraph uses ThreadLocal transactions, spawned thread tx not auto-closed on graph close
+org.apache.tinkerpop.gremlin.structure.TransactionTest.shouldAllowReferenceOfVertexIdOutsideOfOriginalThreadManual: HugeGraph uses ThreadLocal transactions, spawned thread tx not auto-closed on graph close
+
+## HG-LEGACY/TP37-SEMANTICS/FOLLOW-UP: TransactionMultiThreadedTest assumes spawned transaction isolation and cleanup
+org.apache.tinkerpop.gremlin.structure.TransactionMultiThreadedTest.shouldCommit: Memory backend exposes uncommitted vertices across ThreadLocal transactions and spawned tx stays open
+org.apache.tinkerpop.gremlin.structure.TransactionMultiThreadedTest.shouldDeleteVertexOnCommit: Spawned read transaction is not auto-closed after thread exit
+org.apache.tinkerpop.gremlin.structure.TransactionMultiThreadedTest.shouldDeleteRelatedEdgesOnVertexDelete: Spawned verification transaction is not auto-closed after thread exit
+org.apache.tinkerpop.gremlin.structure.TransactionMultiThreadedTest.shouldRollbackAddedVertex: Memory backend exposes uncommitted vertices and spawned tx stays open
+org.apache.tinkerpop.gremlin.structure.TransactionMultiThreadedTest.shouldCommitEdge: Spawned read transactions are not auto-closed after thread exit
+org.apache.tinkerpop.gremlin.structure.TransactionMultiThreadedTest.shouldRollbackAddedEdge: Spawned read transactions are not auto-closed after thread exit
+
+## HG-LEGACY/TP37-SEMANTICS/FOLLOW-UP: MergeEdgeTest hard-codes numeric vertex ids although HugeGraph does not advertise numeric id support
+org.apache.tinkerpop.gremlin.process.traversal.step.map.MergeEdgeTest.Traversals.g_mergeEXlabel_knows_out_marko_in_vadasX: Test requires raw numeric vertex ids while HugeGraph exposes custom Id values
+org.apache.tinkerpop.gremlin.process.traversal.step.map.MergeEdgeTest.Traversals.g_mergeEXlabel_knows_out_marko_in_vadas_weight_05X_exists: Test requires raw numeric vertex ids while HugeGraph exposes custom Id values
+org.apache.tinkerpop.gremlin.process.traversal.step.map.MergeEdgeTest.Traversals.g_mergeEXlabel_knows_out_marko_in_vadasX_optionXonCreate_created_YX_optionXonMatch_created_NX_exists: Test requires raw numeric vertex ids while HugeGraph exposes custom Id values
+org.apache.tinkerpop.gremlin.process.traversal.step.map.MergeEdgeTest.Traversals.g_mergeEXlabel_knows_out_marko_in_vadasX_optionXonCreate_created_YX_optionXonMatch_created_NX_exists_updated: Test requires raw numeric vertex ids while HugeGraph exposes custom Id values
+org.apache.tinkerpop.gremlin.process.traversal.step.map.MergeEdgeTest.Traversals.g_V_hasXperson_name_marko_X_mergeEXlabel_knowsX_optionXonCreate_created_YX_optionXonMatch_created_NX_exists_updated: Test requires raw numeric vertex ids while HugeGraph exposes custom Id values
+org.apache.tinkerpop.gremlin.process.traversal.step.map.MergeEdgeTest.Traversals.g_injectXlabel_knows_out_marko_in_vadasX_mergeE: Test requires raw numeric vertex ids while HugeGraph exposes custom Id values
+org.apache.tinkerpop.gremlin.process.traversal.step.map.MergeEdgeTest.Traversals.g_mergeE_with_outV_inV_options: Test requires raw numeric vertex ids while HugeGraph exposes custom Id values
+
+## HG-LEGACY/FOLLOW-UP: vertex properties don't have nested structures with HugeVertexSerializer
 org.apache.tinkerpop.gremlin.structure.SerializationTest.GraphSONTest.shouldSerializeVertex: Vertex properties doesn't have nested structures with HugeVertexSerializer
 org.apache.tinkerpop.gremlin.structure.SerializationTest.GraphSONTest.shouldSerializePath: Vertex properties doesn't have nested structures with HugeVertexSerializer
 org.apache.tinkerpop.gremlin.structure.SerializationTest.GraphSONTest.shouldSerializeTree: Vertex properties doesn't have nested structures with HugeVertexSerializer
 
-## update property but no commit, lead there are changes in indexTx, can't do index query
+## HG-LEGACY: update property but no commit leaves changes in indexTx
 org.apache.tinkerpop.gremlin.structure.util.detached.DetachedEdgeTest.shouldConstructDetachedEdge: Can't do index query when there are changes in transaction
 org.apache.tinkerpop.gremlin.structure.util.detached.DetachedEdgeTest.shouldConstructDetachedEdgeAsReference: Can't do index query when there are changes in transaction
 org.apache.tinkerpop.gremlin.structure.util.reference.ReferenceEdgeTest.shouldConstructReferenceEdge: Can't do index query when there are changes in transaction
@@ -94,9 +118,12 @@ org.apache.tinkerpop.gremlin.structure.util.reference.ReferenceEdgeTest.shouldCo
 # unsupported automatic edge id, therefore number of edge is wrong
 org.apache.tinkerpop.gremlin.process.traversal.strategy.decoration.EventStrategyProcessTest.shouldTriggerAddEdge: Unsupported automatic edge id, therefore number of edge is wrong
 org.apache.tinkerpop.gremlin.process.traversal.strategy.decoration.EventStrategyProcessTest.shouldTriggerAddEdgeByPath: Unsupported automatic edge id, therefore number of edge is wrong
+org.apache.tinkerpop.gremlin.process.traversal.strategy.decoration.EventStrategyProcessTest.shouldTriggerAddEdgeViaMergeE: Unsupported automatic edge id/event tracking for mergeE
+org.apache.tinkerpop.gremlin.process.traversal.strategy.decoration.EventStrategyProcessTest.shouldTriggerAddVertexWithPropertyThenPropertyAdded: Unsupported vertex property changed event tracking for addV().property()
 # shouldWriteToMultiplePartitions
 org.apache.tinkerpop.gremlin.process.traversal.strategy.decoration.PartitionStrategyProcessTest.shouldWriteToMultiplePartitions: It's not allowed to query by index when there are uncommitted records
 org.apache.tinkerpop.gremlin.process.traversal.strategy.decoration.PartitionStrategyProcessTest.shouldWriteVerticesToMultiplePartitions: It's not allowed to query by index when there are uncommitted records
+org.apache.tinkerpop.gremlin.process.traversal.strategy.decoration.PartitionStrategyProcessTest.shouldPartitionWithAbstractLambdaChildTraversal: Unsupported partition test schema label
 
 # assert error, long time, reason: replicated edges treated as one
 org.apache.tinkerpop.gremlin.process.traversal.step.map.CountTest.Traversals.g_V_both_both_count: Replicated edges treated as one
@@ -117,7 +144,7 @@ org.apache.tinkerpop.gremlin.process.traversal.step.map.AddVertexTest.Traversals
 org.apache.tinkerpop.gremlin.process.traversal.step.map.AddVertexTest.Traversals.g_addVXanimalX_propertyXname_mateoX_propertyXname_gateoX_propertyXname_cateoX_propertyXage_5X: Expect multi properties after setting single property multi times
 org.apache.tinkerpop.gremlin.process.traversal.step.map.AddVertexTest.Traversals.g_V_addVXanimalX_propertyXname_valuesXnameXX_propertyXname_an_animalX_propertyXvaluesXnameX_labelX: Expect multi properties after setting single property multi times
 
-# unsupported predicate
+## HG-LEGACY/TP37-SEMANTICS/FOLLOW-UP: unsupported has predicates and empty-id semantics
 org.apache.tinkerpop.gremlin.process.traversal.step.filter.HasTest.Traversals.g_V_hasIdXwithoutXemptyXX_count: Unsupported query 'NOT IN []'
 org.apache.tinkerpop.gremlin.process.traversal.step.filter.HasTest.Traversals.g_V_hasIdXemptyX_count: Unsupported query 'hasId(EmptyList)'
 
@@ -126,13 +153,20 @@ org.apache.tinkerpop.gremlin.process.traversal.step.filter.HasTest.Traversals.g_
 org.apache.tinkerpop.gremlin.process.traversal.step.filter.HasTest.Traversals.g_V_hasXname_startingWithXmarXX: Unsupported predicate 'startingWith(mar)'
 org.apache.tinkerpop.gremlin.process.traversal.step.filter.HasTest.Traversals.g_V_hasXage_withoutX27X_count: Unsupported relation 'NEQ'
 org.apache.tinkerpop.gremlin.process.traversal.step.filter.HasTest.Traversals.g_V_hasXperson_name_containingXoX_andXltXmXXX: Unsupported predicate 'containing(o)'
+org.apache.tinkerpop.gremlin.process.traversal.step.filter.HasTest.Traversals.g_V_hasXname_regexXrMarXX: Unsupported predicate 'regex(^mar)'
+org.apache.tinkerpop.gremlin.process.traversal.step.filter.HasTest.Traversals.g_V_hasXname_regexXTinkerXX: Unsupported predicate 'regex(Tinker)'
+org.apache.tinkerpop.gremlin.process.traversal.step.filter.HasTest.Traversals.g_V_hasXname_regexXTinkerUnicodeXX: Unsupported predicate 'regex(Tinker.*)'
 org.apache.tinkerpop.gremlin.process.traversal.step.filter.HasTest.Traversals.g_V_hasXname_not_startingWithXmarXX: Unsupported predicate 'notStartingWith(mar)'
 org.apache.tinkerpop.gremlin.process.traversal.step.filter.HasTest.Traversals.g_V_hasXname_not_containingXarkXX: Unsupported predicate 'notContaining(ark)'
 org.apache.tinkerpop.gremlin.process.traversal.step.filter.HasTest.Traversals.g_V_hasXname_not_endingWithXasXX: Unsupported predicate 'notEndingWith(as)'
+org.apache.tinkerpop.gremlin.process.traversal.step.filter.HasTest.Traversals.g_V_hasXname_notRegexXrMarXX: Unsupported predicate 'notRegex(^mar)'
 org.apache.tinkerpop.gremlin.process.traversal.step.filter.HasTest.Traversals.g_V_hasXage_withoutX27_29X_count: Unsupported relation 'NEQ'
 org.apache.tinkerpop.gremlin.process.traversal.step.filter.HasTest.Traversals.g_V_hasXname_gtXmX_andXcontainingXoXXX: Unsupported predicate 'containing(o)'
 org.apache.tinkerpop.gremlin.process.traversal.step.filter.HasTest.Traversals.g_V_hasXp_neqXvXX: Don't accept query based on properties [p] that are not indexed in any label, may not match not-equal condition
 
+org.apache.tinkerpop.gremlin.process.traversal.strategy.decoration.ElementIdStrategyProcessTest.shouldSetIdOnAddEWithNamePropertyKeySpecifiedAndNameSuppliedAsProperty: Unsupported ElementIdStrategy edge id/property index query
+org.apache.tinkerpop.gremlin.process.traversal.strategy.decoration.ElementIdStrategyProcessTest.shouldGenerateDefaultIdOnGraphAddVWithGeneratedCustomId: Unsupported ElementIdStrategy generated custom id schema
+
 # Unsupport edge label 'created': 'software' -> 'person', has existed an edgelabel (created: person -> software) in this case
 org.apache.tinkerpop.gremlin.process.traversal.step.map.AddEdgeTest.Traversals.g_V_hasXname_markoX_asXaX_outEXcreatedX_asXbX_inV_addEXselectXbX_labelX_toXaX: Unsupport edge from inV to outV
 
diff --git a/hugegraph-server/pom.xml b/hugegraph-server/pom.xml
index 226199cdc8..ac00be7fc9 100644
--- a/hugegraph-server/pom.xml
+++ b/hugegraph-server/pom.xml
@@ -42,7 +42,7 @@
         1.2.17
         2.17.1
         4.13.1
-        3.5.1
+        3.7.6
         2.7
         25.1-jre
         4.5.13
@@ -163,12 +163,12 @@
             
             
                 org.apache.tinkerpop
-                gremlin-groovy-test
-                3.2.11
+                gremlin-driver
+                ${tinkerpop.version}
             
             
                 org.apache.tinkerpop
-                gremlin-driver
+                gremlin-util
                 ${tinkerpop.version}
             
 
diff --git a/hugegraph-store/hg-store-core/pom.xml b/hugegraph-store/hg-store-core/pom.xml
index 0ecf723280..21278f002a 100644
--- a/hugegraph-store/hg-store-core/pom.xml
+++ b/hugegraph-store/hg-store-core/pom.xml
@@ -29,6 +29,10 @@
 
     hg-store-core
 
+    
+        3.7.6
+    
+
     
         
             org.apache.hugegraph
@@ -129,7 +133,7 @@
         
             org.apache.tinkerpop
             gremlin-core
-            3.5.1
+            ${tinkerpop.version}
             
                 
                     org.yaml
@@ -152,7 +156,7 @@
         
             org.apache.tinkerpop
             gremlin-groovy
-            3.5.1
+            ${tinkerpop.version}
             
                 
                     com.github.jeremyh
diff --git a/hugegraph-struct/pom.xml b/hugegraph-struct/pom.xml
index b88d0ae204..78404f939b 100644
--- a/hugegraph-struct/pom.xml
+++ b/hugegraph-struct/pom.xml
@@ -34,7 +34,7 @@
         11
         UTF-8
         25.1-jre
-        3.5.1
+        3.7.6
     
 
     
@@ -50,10 +50,17 @@
             3.0.0
         
 
+        
+            org.apache.tinkerpop
+            gremlin-core
+            ${tinkerpop.version}
+        
+
         
             org.apache.tinkerpop
             gremlin-test
             ${tinkerpop.version}
+            test
         
 
         
@@ -86,7 +93,7 @@
         
             org.apache.tinkerpop
             gremlin-shaded
-            3.5.1
+            ${tinkerpop.version}
         
         
             org.mindrot
diff --git a/hugegraph-struct/src/main/java/org/apache/hugegraph/query/Condition.java b/hugegraph-struct/src/main/java/org/apache/hugegraph/query/Condition.java
index 0d1b7ad05b..e2fe822c9b 100644
--- a/hugegraph-struct/src/main/java/org/apache/hugegraph/query/Condition.java
+++ b/hugegraph-struct/src/main/java/org/apache/hugegraph/query/Condition.java
@@ -30,10 +30,10 @@
 import org.apache.hugegraph.util.DateUtil;
 import org.apache.hugegraph.util.E;
 import org.apache.hugegraph.util.NumericUtil;
+import org.apache.tinkerpop.gremlin.process.traversal.PBiPredicate;
 
 import java.util.*;
 import java.util.function.BiFunction;
-import java.util.function.BiPredicate;
 import java.util.regex.Pattern;
 
 public abstract class Condition {
@@ -199,7 +199,7 @@ public enum ConditionType {
         NOT
     }
 
-    public enum RelationType implements BiPredicate {
+    public enum RelationType implements PBiPredicate {
 
         EQ("==", RelationType::equals),
 
@@ -522,6 +522,11 @@ public String string() {
             return this.operator;
         }
 
+        @Override
+        public String getPredicateName() {
+            return this.operator;
+        }
+
         private void checkBaseType(Object value, Class clazz) {
             if (!clazz.isInstance(value)) {
                 String valueClass = value == null ? "null" :
diff --git a/pom.xml b/pom.xml
index 045a24ad56..a159df1c7e 100644
--- a/pom.xml
+++ b/pom.xml
@@ -88,6 +88,8 @@
     
         5.6.0
         1.7.0
+        4.0.25
+        1.28
         1.18.30
         hugegraph
         11
@@ -109,6 +111,18 @@
 
     
         
+            
+                org.apache.groovy
+                groovy-bom
+                ${groovy.version}
+                pom
+                import
+            
+            
+                org.yaml
+                snakeyaml
+                ${snakeyaml.version}
+            
             
                 org.projectlombok
                 lombok