This is an automated email from the ASF dual-hosted git repository.

imbajin pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/hugegraph-toolchain.git

commit ce43b77d7be00f8e82e35e46ba987a270036a96f
Author: dark <[email protected]>
AuthorDate: Fri Aug 14 12:39:02 2026 +0800

    fix(hubble): harden sample schema compatibility
---
 .../controller/graph/SampleGraphController.java    | 160 ++++++++++++++++++++-
 .../src/main/resources/i18n/messages.properties    |   5 +-
 .../main/resources/i18n/messages_zh_CN.properties  |   4 +-
 3 files changed, 163 insertions(+), 6 deletions(-)

diff --git 
a/hugegraph-hubble/hubble-be/src/main/java/org/apache/hugegraph/controller/graph/SampleGraphController.java
 
b/hugegraph-hubble/hubble-be/src/main/java/org/apache/hugegraph/controller/graph/SampleGraphController.java
index 0e49ead22..035d95256 100644
--- 
a/hugegraph-hubble/hubble-be/src/main/java/org/apache/hugegraph/controller/graph/SampleGraphController.java
+++ 
b/hugegraph-hubble/hubble-be/src/main/java/org/apache/hugegraph/controller/graph/SampleGraphController.java
@@ -23,6 +23,9 @@ import java.io.InputStream;
 import java.io.InputStreamReader;
 import java.nio.charset.StandardCharsets;
 import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashMap;
 import java.util.HashSet;
 import java.util.LinkedHashMap;
 import java.util.List;
@@ -41,6 +44,13 @@ import org.apache.hugegraph.controller.BaseController;
 import org.apache.hugegraph.driver.HugeClient;
 import org.apache.hugegraph.driver.SchemaManager;
 import org.apache.hugegraph.exception.ExternalException;
+import org.apache.hugegraph.structure.constant.Cardinality;
+import org.apache.hugegraph.structure.constant.DataType;
+import org.apache.hugegraph.structure.constant.Frequency;
+import org.apache.hugegraph.structure.constant.IdStrategy;
+import org.apache.hugegraph.structure.schema.EdgeLabel;
+import org.apache.hugegraph.structure.schema.PropertyKey;
+import org.apache.hugegraph.structure.schema.VertexLabel;
 import org.apache.hugegraph.util.Ex;
 
 @RestController
@@ -152,8 +162,10 @@ public class SampleGraphController extends BaseController {
         Ex.check(loader || rank || hlm, "common.param.should-belong-to",
                  "dataset", "[loader, hlm, rank]");
         HugeClient client = this.authGremlinClient(graphSpace, graph);
+        SchemaManager schema = client.schema();
+        validateSchemaCompatibility(schema, dataset);
         try {
-            createSchema(client, dataset);
+            createSchema(schema, dataset);
             String script = IDEMPOTENT_TRAVERSAL_FALLBACK_MARKER +
                             data(dataset);
             client.gremlin().gremlin(script).execute();
@@ -176,8 +188,7 @@ public class SampleGraphController extends BaseController {
         return result;
     }
 
-    private static void createSchema(HugeClient client, String dataset) {
-        SchemaManager schema = client.schema();
+    private static void createSchema(SchemaManager schema, String dataset) {
         Set<String> propertyKeys = new HashSet<>();
         schema.getPropertyKeys().forEach(key -> propertyKeys.add(key.name()));
         Set<String> vertexLabels = new HashSet<>();
@@ -193,6 +204,149 @@ public class SampleGraphController extends BaseController 
{
         }
     }
 
+    private static void validateSchemaCompatibility(SchemaManager schema,
+                                                    String dataset) {
+        Map<String, PropertyKey> propertyKeys = new HashMap<>();
+        schema.getPropertyKeys().forEach(key -> propertyKeys.put(key.name(), 
key));
+        Map<String, VertexLabel> vertexLabels = new HashMap<>();
+        schema.getVertexLabels().forEach(label ->
+                vertexLabels.put(label.name(), label));
+        Map<String, EdgeLabel> edgeLabels = new HashMap<>();
+        schema.getEdgeLabels().forEach(label ->
+                edgeLabels.put(label.name(), label));
+
+        if ("loader".equals(dataset)) {
+            validateLoaderSchema(dataset, propertyKeys, vertexLabels, 
edgeLabels);
+        } else if ("rank".equals(dataset)) {
+            validateRankSchema(dataset, propertyKeys, vertexLabels, 
edgeLabels);
+        } else {
+            validateHlmSchema(dataset, propertyKeys, vertexLabels, edgeLabels);
+        }
+    }
+
+    private static void validateLoaderSchema(
+            String dataset, Map<String, PropertyKey> propertyKeys,
+            Map<String, VertexLabel> vertexLabels,
+            Map<String, EdgeLabel> edgeLabels) {
+        requireProperty(dataset, propertyKeys, "name", DataType.TEXT);
+        requireProperty(dataset, propertyKeys, "age", DataType.INT);
+        requireProperty(dataset, propertyKeys, "city", DataType.TEXT);
+        requireProperty(dataset, propertyKeys, "weight", DataType.DOUBLE);
+        requireProperty(dataset, propertyKeys, "lang", DataType.TEXT);
+        requireProperty(dataset, propertyKeys, "date", DataType.TEXT);
+        requireProperty(dataset, propertyKeys, "price", DataType.DOUBLE);
+        requireVertex(dataset, vertexLabels, "person", IdStrategy.PRIMARY_KEY,
+                      names("name", "age", "city"),
+                      Collections.singletonList("name"),
+                      names("age", "city"));
+        requireVertex(dataset, vertexLabels, "software",
+                      IdStrategy.CUSTOMIZE_NUMBER,
+                      names("name", "lang", "price"),
+                      Collections.emptyList(), Collections.emptySet());
+        requireEdge(dataset, edgeLabels, "knows", "person", "person",
+                    names("date", "weight"));
+        requireEdge(dataset, edgeLabels, "created", "person", "software",
+                    names("date", "weight"));
+    }
+
+    private static void validateRankSchema(
+            String dataset, Map<String, PropertyKey> propertyKeys,
+            Map<String, VertexLabel> vertexLabels,
+            Map<String, EdgeLabel> edgeLabels) {
+        requireProperty(dataset, propertyKeys, "name", DataType.TEXT);
+        requireVertex(dataset, vertexLabels, "person",
+                      IdStrategy.CUSTOMIZE_STRING,
+                      names("name"), Collections.emptyList(),
+                      Collections.emptySet());
+        requireVertex(dataset, vertexLabels, "movie",
+                      IdStrategy.CUSTOMIZE_STRING,
+                      names("name"), Collections.emptyList(),
+                      Collections.emptySet());
+        requireEdge(dataset, edgeLabels, "follow", "person", "person",
+                    Collections.emptySet());
+        requireEdge(dataset, edgeLabels, "like", "person", "movie",
+                    Collections.emptySet());
+        requireEdge(dataset, edgeLabels, "directedBy", "movie", "person",
+                    Collections.emptySet());
+    }
+
+    private static void validateHlmSchema(
+            String dataset, Map<String, PropertyKey> propertyKeys,
+            Map<String, VertexLabel> vertexLabels,
+            Map<String, EdgeLabel> edgeLabels) {
+        requireProperty(dataset, propertyKeys, "name", DataType.TEXT);
+        requireProperty(dataset, propertyKeys, "gender", DataType.TEXT);
+        requireProperty(dataset, propertyKeys, "age", DataType.INT);
+        requireProperty(dataset, propertyKeys, "title", DataType.TEXT);
+        requireProperty(dataset, propertyKeys, "feature", DataType.TEXT);
+        requireProperty(dataset, propertyKeys, "intimacy", DataType.TEXT);
+        requireVertex(dataset, vertexLabels, "人物", IdStrategy.PRIMARY_KEY,
+                      names("name", "gender", "age", "title", "feature"),
+                      Collections.singletonList("name"),
+                      Collections.emptySet());
+        requireEdge(dataset, edgeLabels, "关系", "人物", "人物",
+                    names("intimacy"));
+    }
+
+    private static void requireProperty(String dataset,
+                                        Map<String, PropertyKey> existing,
+                                        String name, DataType dataType) {
+        PropertyKey propertyKey = existing.get(name);
+        if (propertyKey != null &&
+            (propertyKey.dataType() != dataType ||
+             propertyKey.cardinality() != Cardinality.SINGLE)) {
+            incompatible(dataset, "property key", name);
+        }
+    }
+
+    private static void requireVertex(String dataset,
+                                      Map<String, VertexLabel> existing,
+                                      String name, IdStrategy idStrategy,
+                                      Set<String> properties,
+                                      List<String> primaryKeys,
+                                      Set<String> nullableKeys) {
+        VertexLabel vertexLabel = existing.get(name);
+        if (vertexLabel != null &&
+            (vertexLabel.idStrategy() != idStrategy ||
+             !vertexLabel.properties().equals(properties) ||
+             !vertexLabel.primaryKeys().equals(primaryKeys) ||
+             !vertexLabel.nullableKeys().containsAll(nullableKeys) ||
+             !hasDefaultTtl(vertexLabel.ttl(), vertexLabel.ttlStartTime()))) {
+            incompatible(dataset, "vertex label", name);
+        }
+    }
+
+    private static void requireEdge(String dataset,
+                                    Map<String, EdgeLabel> existing,
+                                    String name, String source, String target,
+                                    Set<String> properties) {
+        EdgeLabel edgeLabel = existing.get(name);
+        Map<String, String> expectedLink = new HashMap<>();
+        expectedLink.put(source, target);
+        if (edgeLabel != null &&
+            (!edgeLabel.properties().equals(properties) ||
+             edgeLabel.links().size() != 1 ||
+             !edgeLabel.links().contains(expectedLink) ||
+             edgeLabel.frequency() == Frequency.MULTIPLE ||
+             !edgeLabel.sortKeys().isEmpty() ||
+             !hasDefaultTtl(edgeLabel.ttl(), edgeLabel.ttlStartTime()))) {
+            incompatible(dataset, "edge label", name);
+        }
+    }
+
+    private static boolean hasDefaultTtl(long ttl, String ttlStartTime) {
+        return ttl == 0L && ttlStartTime == null;
+    }
+
+    private static Set<String> names(String... values) {
+        return new HashSet<>(Arrays.asList(values));
+    }
+
+    private static void incompatible(String dataset, String type, String name) 
{
+        throw new ExternalException("graph.sample.schema-incompatible",
+                                    dataset, type, name);
+    }
+
     private static void createLoaderSchema(SchemaManager schema,
                                            Set<String> propertyKeys,
                                            Set<String> vertexLabels,
diff --git 
a/hugegraph-hubble/hubble-be/src/main/resources/i18n/messages.properties 
b/hugegraph-hubble/hubble-be/src/main/resources/i18n/messages.properties
index e4b96eb0c..82c9a750b 100644
--- a/hugegraph-hubble/hubble-be/src/main/resources/i18n/messages.properties
+++ b/hugegraph-hubble/hubble-be/src/main/resources/i18n/messages.properties
@@ -68,6 +68,7 @@ graph.import.file.exceed-limit=The import file size {0} 
exceeds limit {1}
 graph.import.missing-field=The import field {0} is required
 graph.import.field-should-array=The import field {0} should be an array
 graph.sample.load-failed=Failed to load sample {0} into {1}/{2}. Existing data 
was not cleared. Check schema or ID conflicts, then retry.
+graph.sample.schema-incompatible=Example {0} was not imported because the 
existing {1} ''{2}'' is incompatible. No Schema or data was changed; use an 
empty or dedicated graph.
 graph.import.vertex.duplicate-id=The import vertex id {0} is duplicated
 
 gremlin-collection.name.unmatch-regex=Invalid gremlin statement name, valid 
name is up to 48 alpha-numeric characters and underscores
@@ -244,8 +245,8 @@ license.verify.mac.unauthorized=The hugegraph-hubble's mac 
{0} doesn't match the
 
 https.load.truststore.error=Failed to load https trusted certificate
 
-service.no-available=No service available
-service.graphspace.no-available=No service available
+service.no-available=Unable to connect to HugeGraph Server. Check the cluster 
connection and try again
+service.graphspace.no-available=Unable to connect to HugeGraph Server. Check 
the cluster connection and try again
 service.default.no-available=No service avaialbe under namespace named 
'DEFAULT'
 service.url.parse.error=Parse host info ({0}) error?please check the url in \
   service config
diff --git 
a/hugegraph-hubble/hubble-be/src/main/resources/i18n/messages_zh_CN.properties 
b/hugegraph-hubble/hubble-be/src/main/resources/i18n/messages_zh_CN.properties
index beb88bbe0..7b1f28aef 100644
--- 
a/hugegraph-hubble/hubble-be/src/main/resources/i18n/messages_zh_CN.properties
+++ 
b/hugegraph-hubble/hubble-be/src/main/resources/i18n/messages_zh_CN.properties
@@ -68,6 +68,7 @@ graph.import.file.exceed-limit=导入文件大小 {0} 超过限制 {1}
 graph.import.missing-field=导入字段 {0} 必填
 graph.import.field-should-array=导入字段 {0} 应为数组
 graph.sample.load-failed=示例 {0} 无法导入 {1}/{2};既有数据未被清空。请检查 Schema 或 ID 冲突后重试。
+graph.sample.schema-incompatible=示例 {0} 未导入:现有{1}“{2}”不兼容。Schema 
和数据均未修改,请使用空图或专用图。
 graph.import.vertex.duplicate-id=导入顶点 id {0} 重复
 
 gremlin-collection.name.unmatch-regex=语句名不合法,语句名允许字母、数字、中文、下划线,最多 48 个字符
@@ -244,7 +245,8 @@ license.verify.mac.unauthorized=hugegraph-hubble 机器的 MAC 
{0} 不在已授
 
 https.load.truststore.error=https 可信证书加载失败
 
-service.no-available=当前无可用服务
+service.no-available=暂时无法连接到 HugeGraph Server,请检查集群连接后重试
+service.graphspace.no-available=暂时无法连接到 HugeGraph Server,请检查集群连接后重试
 service.default.no-available=DEFAULT命名空间下的DEFAULT服务无法使用
 service.url.parse.error="无法解析的主机名或 IP ({0}) ,请修改相关服务的URL配置"
 service.manual.disable.modify=禁止修改手动启动的图服务

Reply via email to