diff --git a/hugegraph-core/src/main/java/com/baidu/hugegraph/backend/query/ConditionQuery.java b/hugegraph-core/src/main/java/com/baidu/hugegraph/backend/query/ConditionQuery.java index d083b8f674..3cef7a0d2f 100644 --- a/hugegraph-core/src/main/java/com/baidu/hugegraph/backend/query/ConditionQuery.java +++ b/hugegraph-core/src/main/java/com/baidu/hugegraph/backend/query/ConditionQuery.java @@ -142,6 +142,15 @@ public List relations() { return relations; } + public Relation relation(Id key){ + for (Relation r : this.relations()) { + if (r.key().equals(key)) { + return r; + } + } + return null; + } + @Watched public T condition(Object key) { List values = new ArrayList<>(); @@ -209,6 +218,16 @@ public boolean containsScanCondition() { return this.containsCondition(Condition.RelationType.SCAN); } + public boolean containsContainsCondition(Id key) { + for (Relation r : this.relations()) { + if (r.key().equals(key)) { + return r.relation().equals(RelationType.CONTAINS) || + r.relation().equals(RelationType.TEXT_CONTAINS); + } + } + return false; + } + public boolean allSysprop() { for (Condition c : this.conditions) { if (!c.isSysprop()) { @@ -310,10 +329,11 @@ public String userpropValuesString(List fields) { boolean got = false; for (Relation r : this.userpropRelations()) { if (r.key().equals(field) && !r.isSysprop()) { - E.checkState(r.relation == RelationType.EQ, + E.checkState(r.relation == RelationType.EQ || + r.relation == RelationType.CONTAINS, "Method userpropValues(List) only " + "used for secondary index, " + - "relation must be EQ, but got %s", + "relation must be EQ or CONTAINS, but got %s", r.relation()); values.add(r.serialValue()); got = true; @@ -507,14 +527,29 @@ public static String concatValues(List values) { return SplicingIdGenerator.concatValues(newValues); } + public static String concatValues(Object value) { + if (value instanceof List) { + return concatValues((List)value); + } + + if (needConvertNumber(value)) { + return LongEncoding.encodeNumber(value); + } + return value.toString(); + } + private static Object convertNumberIfNeeded(Object value) { - if (NumericUtil.isNumber(value) || value instanceof Date) { - // Numeric or date values should be converted to string + if (needConvertNumber(value)) { return LongEncoding.encodeNumber(value); } return value; } + private static boolean needConvertNumber(Object value) { + // Numeric or date values should be converted to number from string + return NumericUtil.isNumber(value) || value instanceof Date; + } + public enum OptimizedType { NONE, PRIMARY_KEY, diff --git a/hugegraph-core/src/main/java/com/baidu/hugegraph/backend/tx/GraphIndexTransaction.java b/hugegraph-core/src/main/java/com/baidu/hugegraph/backend/tx/GraphIndexTransaction.java index e59d217004..32ee0a5135 100644 --- a/hugegraph-core/src/main/java/com/baidu/hugegraph/backend/tx/GraphIndexTransaction.java +++ b/hugegraph-core/src/main/java/com/baidu/hugegraph/backend/tx/GraphIndexTransaction.java @@ -33,6 +33,7 @@ import java.util.function.Function; import java.util.stream.Collectors; +import org.apache.commons.lang3.StringUtils; import org.apache.logging.log4j.util.Strings; import org.apache.tinkerpop.gremlin.structure.Edge; import org.apache.tinkerpop.gremlin.structure.Vertex; @@ -227,7 +228,8 @@ protected void updateIndex(Id ilId, HugeElement element, boolean removed) { E.checkState(propValues.size() == 1, "Expect only one property in search index"); value = propValues.get(0); - Set words = this.segmentWords(value.toString()); + Set words = + this.segmentWords(propertyValueToString(value)); for (String word : words) { this.updateIndex(indexLabel, word, element.id(), expiredTime, removed); @@ -235,12 +237,27 @@ protected void updateIndex(Id ilId, HugeElement element, boolean removed) { break; case SECONDARY: // Secondary index maybe include multi prefix index - for (int i = 0, n = propValues.size(); i < n; i++) { - List prefixValues = propValues.subList(0, i + 1); - value = ConditionQuery.concatValues(prefixValues); - value = escapeIndexValueIfNeeded((String) value); - this.updateIndex(indexLabel, value, element.id(), - expiredTime, removed); + if (isCollectionIndex(propValues)) { + /* + * Property value is a collection + * we should create index for each item + */ + for (Object propValue : + (Collection) propValues.get(0)) { + value = ConditionQuery.concatValues(propValue); + value = escapeIndexValueIfNeeded((String) value); + this.updateIndex(indexLabel, value, element.id(), + expiredTime, removed); + } + } else { + for (int i = 0, n = propValues.size(); i < n; i++) { + List prefixValues = + propValues.subList(0, i + 1); + value = ConditionQuery.concatValues(prefixValues); + value = escapeIndexValueIfNeeded((String) value); + this.updateIndex(indexLabel, value, element.id(), + expiredTime, removed); + } } break; case SHARD: @@ -766,9 +783,11 @@ private ConditionQuery constructSearchQuery(ConditionQuery query, if (key instanceof Id && indexFields.contains(key)) { // This is an index field of search index Id field = (Id) key; - String propValue = elem.getPropertyValue(field); - String fvalue = (String) originQuery.userpropValue(field); - if (this.matchSearchIndexWords(propValue, fvalue)) { + assert elem != null; + HugeProperty property = elem.getProperty(field); + String propValue = propertyValueToString(property.value()); + String fieldValue = (String) originQuery.userpropValue(field); + if (this.matchSearchIndexWords(propValue, fieldValue)) { continue; } return false; @@ -1310,7 +1329,20 @@ private static boolean validQueryConditionValues(HugeGraph graph, E.checkState(!values.isEmpty(), "Expect user property values for key '%s', " + "but got none", pk); + boolean hasContains = query.containsContainsCondition(key); + if (pk.cardinality().multiple()) { + // If contains collection index, relation should be contains + E.checkState(hasContains, + "The relation of property '%s' must be " + + "CONTAINS or TEXT_CONTAINS, but got %s", + pk.name(), query.relation(key).relation()); + } + for (Object value : values) { + if (hasContains) { + value = toCollectionIfNeeded(pk, value); + } + if (!pk.checkValueType(value)) { return false; } @@ -1319,6 +1351,39 @@ private static boolean validQueryConditionValues(HugeGraph graph, return true; } + private static Object toCollectionIfNeeded(PropertyKey pk, Object value) { + switch (pk.cardinality()) { + case SET: + if (!(value instanceof Set)) { + value = CollectionUtil.toSet(value); + } + break; + case LIST: + if (!(value instanceof List)) { + value = CollectionUtil.toList(value); + } + break; + default: + break; + } + return value; + } + + private static boolean isCollectionIndex(List propValues) { + return propValues.size() == 1 && + propValues.get(0) instanceof Collection; + } + + private static String propertyValueToString(Object value) { + /* + * Join collection items with white space if the value is Collection, + * or else keep the origin value. + */ + return value instanceof Collection ? + StringUtils.join(((Collection) value).toArray(), " ") : + value.toString(); + } + private static String escapeIndexValueIfNeeded(String value) { for (int i = 0; i < value.length(); i++) { char ch = value.charAt(i); diff --git a/hugegraph-core/src/main/java/com/baidu/hugegraph/schema/IndexLabel.java b/hugegraph-core/src/main/java/com/baidu/hugegraph/schema/IndexLabel.java index 02ae255ad9..172ffb5ef4 100644 --- a/hugegraph-core/src/main/java/com/baidu/hugegraph/schema/IndexLabel.java +++ b/hugegraph-core/src/main/java/com/baidu/hugegraph/schema/IndexLabel.java @@ -112,9 +112,6 @@ public void indexField(Id id) { } public Id indexField() { - E.checkState(this.indexType.isRange() || this.indexType.isSearch(), - "Can't call indexField() for %s index label", - this.indexType.string()); E.checkState(this.indexFields.size() == 1, "There should be only one field in %s index label, " + "but got: %s", this.indexType.string(), this.indexFields); diff --git a/hugegraph-core/src/main/java/com/baidu/hugegraph/schema/builder/IndexLabelBuilder.java b/hugegraph-core/src/main/java/com/baidu/hugegraph/schema/builder/IndexLabelBuilder.java index 1ee278e745..2c7ca237ac 100644 --- a/hugegraph-core/src/main/java/com/baidu/hugegraph/schema/builder/IndexLabelBuilder.java +++ b/hugegraph-core/src/main/java/com/baidu/hugegraph/schema/builder/IndexLabelBuilder.java @@ -466,10 +466,13 @@ private void checkFields(Set propertyIds) { E.checkArgument(pkey.aggregateType().isIndexable(), "The aggregate type %s is not indexable", pkey.aggregateType()); - E.checkArgument(pkey.cardinality().single(), - "Not allowed to build index on property key " + - "'%s' whose cardinality is list or set", - pkey.name()); + + if (pkey.cardinality().multiple()) { + E.checkArgument(fields.size() == 1, + "Not allowed to build union index on property" + + " key '%s' whose cardinality is multiple", + pkey.name()); + } } List properties = this.graph().mapPkId2Name(propertyIds); @@ -506,6 +509,10 @@ private void checkFields4Range() { "one field, but got %s fields: '%s'", fields.size(), fields); String field = fields.iterator().next(); + PropertyKey property = this.graph().propertyKey(field); + E.checkArgument(!property.cardinality().multiple(), + "Not allowed to build range index on property " + + "'%s' whose cardinality is multiple", field); DataType dataType = this.graph().propertyKey(field).dataType(); E.checkArgument(dataType.isNumber() || dataType.isDate(), "Range index can only build on numeric or " + diff --git a/hugegraph-test/src/main/java/com/baidu/hugegraph/core/EdgeCoreTest.java b/hugegraph-test/src/main/java/com/baidu/hugegraph/core/EdgeCoreTest.java index d0aef334aa..60cf9710b2 100644 --- a/hugegraph-test/src/main/java/com/baidu/hugegraph/core/EdgeCoreTest.java +++ b/hugegraph-test/src/main/java/com/baidu/hugegraph/core/EdgeCoreTest.java @@ -70,6 +70,7 @@ import com.baidu.hugegraph.testutil.FakeObjects.FakeEdge; import com.baidu.hugegraph.testutil.Utils; import com.baidu.hugegraph.testutil.Whitebox; +import com.baidu.hugegraph.traversal.optimize.ConditionP; import com.baidu.hugegraph.traversal.optimize.Text; import com.baidu.hugegraph.traversal.optimize.TraversalUtil; import com.baidu.hugegraph.type.HugeType; @@ -7055,6 +7056,103 @@ public void testQueryByHasIdEmptyListInPage() { Assert.assertNull(page); } + @Test + public void testAddEdgeWithCollectionIndex() { + SchemaManager schema = graph().schema(); + schema.propertyKey("tags").asText().valueSet().create(); + schema.propertyKey("category").asText().valueSet().create(); + schema.propertyKey("country").asText().create(); + + schema.vertexLabel("soft") + .properties("name", "tags", "country", "category") + .primaryKeys("name").create(); + + schema.edgeLabel("related") + .sourceLabel("soft") + .targetLabel("soft") + .properties("tags").create(); + + schema.indexLabel("edgeByTag").onE("related").secondary() + .by("tags") + .create(); + + HugeGraph graph = graph(); + + Vertex huge = graph.addVertex( + T.label, "soft", + "name", "hugegraph", + "country", "china", + "category", ImmutableList.of("graphdb", "db"), + "tags", ImmutableList.of("graphdb", "gremlin")); + + Vertex neo4j = graph.addVertex( + T.label, "soft", "name", "neo4j", + "country", "usa", + "category", ImmutableList.of("graphdb", "db"), + "tags", ImmutableList.of("graphdb", "cypher")); + + Vertex janus = graph.addVertex( + T.label, "soft", "name", "janusgraph", + "country", "usa", + "category", ImmutableList.of("graphdb", "db"), + "tags", ImmutableList.of("graphdb", "gremlin")); + + huge.addEdge("related", neo4j, "tags", ImmutableList.of("graphdb")); + + Edge huge2janus = huge.addEdge("related", janus, "tags", + ImmutableList.of("graphdb", "gremlin")); + + graph.tx().commit(); + + Assert.assertThrows(IllegalStateException.class, () -> { + graph.traversal().E().has("related", "tags", + "gremlin").toList(); + }); + + + List edges = graph.traversal().E() + .has("related","tags", + ConditionP.contains("gremlin")) + .toList(); + + Assert.assertEquals(1, edges.size()); + + edges = graph.traversal().E() + .has("related", + "tags", ConditionP.contains("graphdb")) + .toList(); + + Assert.assertEquals(2, edges.size()); + + // append a new tag + huge2janus.property("tags", ImmutableList.of("newTag")); + graph.tx().commit(); + + edges = graph.traversal().E() + .has("related", + "tags", ConditionP.contains("newTag")) + .toList(); + + Assert.assertEquals(1, edges.size()); + + edges = graph.traversal().E() + .has("related", + "tags", ConditionP.contains("graphdb")) + .toList(); + + Assert.assertEquals(2, edges.size()); + + // remove a edge + edges.get(0).remove(); + graph.tx().commit(); + + edges = graph.traversal().E() + .has("related", + "tags", ConditionP.contains("graphdb")) + .toList(); + Assert.assertEquals(1, edges.size()); + } + private void init18Edges() { this.init18Edges(true); } diff --git a/hugegraph-test/src/main/java/com/baidu/hugegraph/core/IndexLabelCoreTest.java b/hugegraph-test/src/main/java/com/baidu/hugegraph/core/IndexLabelCoreTest.java index ccf9c8921b..f8556b6a48 100644 --- a/hugegraph-test/src/main/java/com/baidu/hugegraph/core/IndexLabelCoreTest.java +++ b/hugegraph-test/src/main/java/com/baidu/hugegraph/core/IndexLabelCoreTest.java @@ -55,8 +55,13 @@ public void testAddIndexLabelOfVertex() { schema.propertyKey("fans").asLong().ifNotExist().create(); schema.propertyKey("height").asFloat().ifNotExist().create(); schema.propertyKey("idNo").asText().ifNotExist().create(); + schema.propertyKey("category").asText().valueSet().ifNotExist() + .create(); + schema.propertyKey("score").asInt().valueSet().ifNotExist() + .create(); schema.vertexLabel("person") - .properties("id", "name", "age", "city", "born", + .properties("id", "name", "age", "city", "born", "tags", + "category", "score", "fans", "height", "weight", "idNo") .primaryKeys("id").create(); schema.indexLabel("personByName").onV("person").secondary() @@ -75,6 +80,12 @@ public void testAddIndexLabelOfVertex() { .by("weight").create(); schema.indexLabel("personByIdNo").onV("person").unique() .by("idNo").create(); + schema.indexLabel("personByTags").onV("person").secondary() + .by("tags").create(); + schema.indexLabel("personByCategory").onV("person").search() + .by("category").create(); + schema.indexLabel("personByScore").onV("person").secondary() + .by("score").create(); VertexLabel person = schema.getVertexLabel("person"); IndexLabel personByName = schema.getIndexLabel("personByName"); @@ -85,6 +96,9 @@ public void testAddIndexLabelOfVertex() { IndexLabel personByHeight = schema.getIndexLabel("personByHeight"); IndexLabel personByWeight = schema.getIndexLabel("personByWeight"); IndexLabel personByIdNo = schema.getIndexLabel("personByIdNo"); + IndexLabel personByTags = schema.getIndexLabel("personByTags"); + IndexLabel personByCategory = schema.getIndexLabel("personByCategory"); + IndexLabel personByScore = schema.getIndexLabel("personByScore"); Assert.assertNotNull(personByName); Assert.assertNotNull(personByCity); @@ -94,12 +108,15 @@ public void testAddIndexLabelOfVertex() { Assert.assertNotNull(personByHeight); Assert.assertNotNull(personByWeight); Assert.assertNotNull(personByIdNo); + Assert.assertNotNull(personByTags); + Assert.assertNotNull(personByCategory); - Assert.assertEquals(8, person.indexLabels().size()); + Assert.assertEquals(11, person.indexLabels().size()); assertContainsIl(person.indexLabels(), "personByName", "personByCity", "personByAge", "personByBorn", "personByFans","personByHeight", - "personByWeight", "personByIdNo"); + "personByWeight", "personByIdNo", "personByTags", + "personByCategory", "personByScore"); Assert.assertEquals(HugeType.VERTEX_LABEL, personByName.baseType()); Assert.assertEquals(HugeType.VERTEX_LABEL, personByCity.baseType()); @@ -109,6 +126,9 @@ public void testAddIndexLabelOfVertex() { Assert.assertEquals(HugeType.VERTEX_LABEL, personByHeight.baseType()); Assert.assertEquals(HugeType.VERTEX_LABEL, personByWeight.baseType()); Assert.assertEquals(HugeType.VERTEX_LABEL, personByIdNo.baseType()); + Assert.assertEquals(HugeType.VERTEX_LABEL, personByTags.baseType()); + Assert.assertEquals(HugeType.VERTEX_LABEL, personByCategory.baseType()); + Assert.assertEquals(HugeType.VERTEX_LABEL, personByScore.baseType()); assertVLEqual("person", personByName.baseValue()); assertVLEqual("person", personByCity.baseValue()); @@ -118,6 +138,9 @@ public void testAddIndexLabelOfVertex() { assertVLEqual("person", personByHeight.baseValue()); assertVLEqual("person", personByWeight.baseValue()); assertVLEqual("person", personByIdNo.baseValue()); + assertVLEqual("person", personByTags.baseValue()); + assertVLEqual("person", personByCategory.baseValue()); + assertVLEqual("person", personByScore.baseValue()); Assert.assertEquals(IndexType.SECONDARY, personByName.indexType()); Assert.assertEquals(IndexType.SEARCH, personByCity.indexType()); @@ -127,6 +150,9 @@ public void testAddIndexLabelOfVertex() { Assert.assertEquals(IndexType.RANGE_FLOAT, personByHeight.indexType()); Assert.assertEquals(IndexType.RANGE_DOUBLE, personByWeight.indexType()); Assert.assertEquals(IndexType.UNIQUE, personByIdNo.indexType()); + Assert.assertEquals(IndexType.SECONDARY, personByTags.indexType()); + Assert.assertEquals(IndexType.SEARCH, personByCategory.indexType()); + Assert.assertEquals(IndexType.SECONDARY, personByScore.indexType()); } @Test @@ -174,21 +200,31 @@ public void testAddIndexLabelOfEdge() { .primaryKeys("name").create(); schema.edgeLabel("authored").singleTime() .link("author", "book") - .properties("contribution") + .properties("contribution", "tags") .create(); schema.indexLabel("authoredByContri").onE("authored").secondary() .by("contribution").create(); + schema.indexLabel("authoredByTags").onE("authored").secondary() + .by("tags").create(); EdgeLabel authored = schema.getEdgeLabel("authored"); IndexLabel authoredByContri = schema.getIndexLabel("authoredByContri"); + IndexLabel authoredByTags = schema.getIndexLabel("authoredByTags"); Assert.assertNotNull(authoredByContri); - Assert.assertEquals(1, authored.indexLabels().size()); - assertContainsIl(authored.indexLabels(), "authoredByContri"); + Assert.assertEquals(2, authored.indexLabels().size()); + assertContainsIl(authored.indexLabels(), "authoredByContri", + "authoredByTags"); + Assert.assertEquals(HugeType.EDGE_LABEL, authoredByContri.baseType()); + Assert.assertEquals(HugeType.EDGE_LABEL, authoredByTags.baseType()); + assertELEqual("authored", authoredByContri.baseValue()); + assertELEqual("authored", authoredByTags.baseValue()); + Assert.assertEquals(IndexType.SECONDARY, authoredByContri.indexType()); + Assert.assertEquals(IndexType.SECONDARY, authoredByTags.indexType()); } @Test @@ -331,6 +367,8 @@ public void testAddIndexLabelWithInvalidFields() { .primaryKeys("id").create(); schema.vertexLabel("book").properties("name") .primaryKeys("name").create(); + schema.vertexLabel("soft").properties("name", "tags", "score") + .primaryKeys("name").create(); schema.edgeLabel("authored").singleTime().link("author", "book") .properties("contribution", "age", "weight").create(); @@ -343,6 +381,23 @@ public void testAddIndexLabelWithInvalidFields() { e.getMessage()); }); + // Collection index not support union index + Assert.assertThrows(IllegalArgumentException.class, () -> { + schema.indexLabel("softByNameAndTags").onV("soft") + .by("name", "tags").secondary().create(); + }, e -> { + Assert.assertContains("Not allowed to build union index", + e.getMessage()); + }); + + Assert.assertThrows(IllegalArgumentException.class, () -> { + schema.indexLabel("softByScore").onV("soft") + .by("score").range().create(); + }, e -> { + Assert.assertContains("Not allowed to build range index", + e.getMessage()); + }); + Assert.assertThrows(IllegalArgumentException.class, () -> { schema.indexLabel("authoredByAgeAndWeight").onE("authored") .by("age", "weight").range().create(); diff --git a/hugegraph-test/src/main/java/com/baidu/hugegraph/core/SchemaCoreTest.java b/hugegraph-test/src/main/java/com/baidu/hugegraph/core/SchemaCoreTest.java index f5f865854e..e47cca06b7 100644 --- a/hugegraph-test/src/main/java/com/baidu/hugegraph/core/SchemaCoreTest.java +++ b/hugegraph-test/src/main/java/com/baidu/hugegraph/core/SchemaCoreTest.java @@ -50,6 +50,8 @@ protected void initPropertyKeys() { schema.propertyKey("time").asText().create(); schema.propertyKey("contribution").asText().create(); schema.propertyKey("weight").asDouble().create(); + schema.propertyKey("tags").asText().valueSet().create(); + schema.propertyKey("score").asInt().valueSet().create(); } protected void assertVLEqual(String label, Id id) { diff --git a/hugegraph-test/src/main/java/com/baidu/hugegraph/core/VertexCoreTest.java b/hugegraph-test/src/main/java/com/baidu/hugegraph/core/VertexCoreTest.java index 3fcbc9f74c..9a53c258b2 100644 --- a/hugegraph-test/src/main/java/com/baidu/hugegraph/core/VertexCoreTest.java +++ b/hugegraph-test/src/main/java/com/baidu/hugegraph/core/VertexCoreTest.java @@ -71,6 +71,7 @@ import com.baidu.hugegraph.testutil.FakeObjects.FakeVertex; import com.baidu.hugegraph.testutil.Utils; import com.baidu.hugegraph.testutil.Whitebox; +import com.baidu.hugegraph.traversal.optimize.ConditionP; import com.baidu.hugegraph.traversal.optimize.Text; import com.baidu.hugegraph.traversal.optimize.TraversalUtil; import com.baidu.hugegraph.type.HugeType; @@ -431,6 +432,136 @@ public void testAddVertexWithPropertySet() { vertex.value("contribution")); } + @Test + public void testAddVertexWithCollectionIndex() { + SchemaManager schema = graph().schema(); + schema.propertyKey("tags").asText().valueSet().create(); + schema.propertyKey("category").asText().valueSet().create(); + schema.propertyKey("country").asText().create(); + schema.propertyKey("score").asInt().valueSet().create(); + + schema.vertexLabel("soft").properties("name", "tags", "score", + "country", "category") + .primaryKeys("name").create(); + + schema.indexLabel("softByTag").onV("soft").secondary() + .by("tags").create(); + + schema.indexLabel("softByCategory").onV("soft").search() + .by("category").create(); + + schema.indexLabel("softByScore").onV("soft").secondary() + .by("score").create(); + + HugeGraph graph = graph(); + + graph.addVertex(T.label, "soft", "name", "hugegraph", + "country", "china", + "score", ImmutableList.of(5, 4, 3), + "category", ImmutableList.of("graph database", "db"), + "tags", ImmutableList.of("graphdb", "gremlin")); + + graph.addVertex(T.label, "soft", "name", "neo4j", + "country", "usa", + "score", ImmutableList.of(5, 4), + "category", ImmutableList.of("graphdb", "database"), + "tags", ImmutableList.of("graphdb", "cypher")); + + graph.addVertex(T.label, "soft", "name", "janusgraph", + "country", "usa", + "score", ImmutableList.of(5), + "category", + ImmutableList.of("hello graph", "graph database"), + "tags", ImmutableList.of("graphdb", "gremlin")); + + graph.tx().commit(); + + List vertices; + vertices = graph.traversal().V() + .has("soft", "category", + ConditionP.textContains("hello database")) + .toList(); + Assert.assertEquals(3, vertices.size()); + + vertices = graph.traversal().V() + .has("soft", "category", + ConditionP.textContains("graph")) + .toList(); + Assert.assertEquals(2, vertices.size()); + + Assert.assertThrows(IllegalStateException.class, () -> { + graph.traversal().V().has("soft", "tags", + "gremlin").toList(); + }); + + // query by contains + vertices = graph.traversal().V() + .has("soft", "tags", + ConditionP.contains("gremlin")) + .toList(); + Assert.assertEquals(2, vertices.size()); + + // secondary-index with list/set of number properties + vertices = graph.traversal().V() + .has("soft", "score", + ConditionP.contains(5)) + .toList(); + Assert.assertEquals(3, vertices.size()); + + vertices = graph.traversal().V() + .has("soft", "name", + "hugegraph").toList(); + Assert.assertEquals(1, vertices.size()); + + // add a new tag + Vertex vertex = vertices.get(0); + vertex.property("tags", ImmutableSet.of("new_tag")); + graph.tx().commit(); + + vertices = graph.traversal().V() + .has("soft", "tags", + ConditionP.contains("new_tag")).toList(); + Assert.assertEquals(1, vertices.size()); + + // delete tag gremlin + vertex = graph.addVertex(T.label, "soft", "name", "hugegraph", + "country", "china", + "score", ImmutableList.of(5, 4, 3), + "category", + ImmutableList.of("hello graph", "graph database"), + "tags", ImmutableList.of("graphdb", "new_tag")); + graph.tx().commit(); + + vertices = graph.traversal().V() + .has("soft", "tags", ConditionP.contains("gremlin")) + .toList(); + Assert.assertEquals(1, vertices.size()); + + vertices = graph.traversal().V() + .has("soft", "tags", ConditionP.contains("new_tag")) + .toList(); + Assert.assertEquals(1, vertices.size()); + + vertices = graph.traversal().V() + .has("soft", "tags", ConditionP.contains("graphdb")) + .toList(); + Assert.assertEquals(3, vertices.size()); + + // remove + vertex.remove(); + graph.tx().commit(); + + vertices = graph.traversal().V() + .has("soft", "tags", ConditionP.contains("new_tag")) + .toList(); + Assert.assertEquals(0, vertices.size()); + + vertices = graph.traversal().V() + .has("soft", "tags", ConditionP.contains("graphdb")) + .toList(); + Assert.assertEquals(2, vertices.size()); + } + @Test public void testAddVertexWithInvalidPropertySet() { HugeGraph graph = graph();