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=禁止修改手动启动的图服务
