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 1be7f3fe63..226b618045 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 @@ -19,6 +19,7 @@ package com.baidu.hugegraph.backend.query; +import java.math.BigDecimal; import java.util.ArrayList; import java.util.Collections; import java.util.Date; @@ -37,6 +38,7 @@ import com.baidu.hugegraph.backend.query.Condition.RelationType; import com.baidu.hugegraph.perf.PerfUtil.Watched; import com.baidu.hugegraph.structure.HugeElement; +import com.baidu.hugegraph.structure.HugeProperty; import com.baidu.hugegraph.type.HugeType; import com.baidu.hugegraph.type.define.CollectionType; import com.baidu.hugegraph.type.define.HugeKeys; @@ -46,6 +48,7 @@ import com.baidu.hugegraph.util.collection.CollectionFactory; import com.google.common.base.Function; import com.google.common.collect.ImmutableSet; +import com.google.common.collect.Sets; public final class ConditionQuery extends IdQuery { @@ -56,6 +59,7 @@ public final class ConditionQuery extends IdQuery { private OptimizedType optimizedType = OptimizedType.NONE; private Function resultsFilter = null; + private Element2IndexValueMap element2IndexValueMap = null; public ConditionQuery(HugeType resultType) { super(resultType); @@ -142,6 +146,34 @@ public void resetConditions() { this.conditions = new LinkedHashSet<>(); } + public void recordIndexValue(Id propertyId, Id id, Object indexValue) { + this.ensureElement2IndexValueMap(); + this.element2IndexValueMap.addIndexValue(propertyId, id, indexValue); + } + + public void selectedIndexField(Id indexField) { + this.ensureElement2IndexValueMap(); + this.element2IndexValueMap.selectedIndexField(indexField); + } + + public Set getElementLeftIndex(Id elementId) { + if (this.element2IndexValueMap == null) { + return null; + } + return this.element2IndexValueMap.getLeftIndex(elementId); + } + + public void removeElementLeftIndex(Id elementId) { + if (this.element2IndexValueMap == null) { + return; + } + this.element2IndexValueMap.removeElementLeftIndex(elementId); + } + + public boolean existLeftIndex(Id elementId) { + return this.getElementLeftIndex(elementId) != null; + } + public List relations() { List relations = new ArrayList<>(); for (Condition c : this.conditions) { @@ -417,7 +449,6 @@ public boolean hasNeqCondition() { return false; } - public boolean matchUserpropKeys(List keys) { Set conditionKeys = this.userpropKeys(); return keys.size() > 0 && conditionKeys.containsAll(keys); @@ -455,13 +486,13 @@ public boolean test(HugeElement element) { if (this.resultsFilter != null) { return this.resultsFilter.apply(element); } - + boolean valid = true; for (Condition cond : this.conditions()) { - if (!cond.test(element)) { - return false; - } + valid &= cond.test(element); + valid &= (this.element2IndexValueMap == null || + this.element2IndexValueMap.validRangeIndex(element, cond)); } - return true; + return valid; } public void checkFlattened() { @@ -530,6 +561,24 @@ public void registerResultsFilter(Function filter) { } } + public ConditionQuery originConditionQuery() { + Query originQuery = this.originQuery(); + if (!(originQuery instanceof ConditionQuery)) { + return null; + } + + while (originQuery.originQuery() instanceof ConditionQuery) { + originQuery = originQuery.originQuery(); + } + return (ConditionQuery) originQuery; + } + + private void ensureElement2IndexValueMap() { + if (this.element2IndexValueMap == null) { + this.element2IndexValueMap = new Element2IndexValueMap(); + } + } + public static String concatValues(List values) { List newValues = new ArrayList<>(values.size()); for (Object v : values) { @@ -568,4 +617,149 @@ public enum OptimizedType { INDEX, INDEX_FILTER } + + public static final class Element2IndexValueMap { + + private final Map> leftIndexMap; + private final Map>> filed2IndexValues; + private Id selectedIndexField; + + public Element2IndexValueMap() { + this.filed2IndexValues = new HashMap<>(); + this.leftIndexMap = new HashMap<>(); + } + + public void addIndexValue(Id indexField, Id elementId, + Object indexValue) { + if (!this.filed2IndexValues.containsKey(indexField)) { + this.filed2IndexValues.put(indexField, new HashMap<>()); + } + Map> element2IndexValueMap = + this.filed2IndexValues.get(indexField); + if (element2IndexValueMap.containsKey(elementId)) { + element2IndexValueMap.get(elementId).add(indexValue); + } else { + element2IndexValueMap.put(elementId, + Sets.newHashSet(indexValue)); + } + } + + public void selectedIndexField(Id indexField) { + this.selectedIndexField = indexField; + } + + public Set removeIndexValues(Id indexField, Id elementId) { + if (!this.filed2IndexValues.containsKey(indexField)) { + return null; + } + return this.filed2IndexValues.get(indexField).get(elementId); + } + + public void addLeftIndex(Id indexField, Set indexValues, + Id elementId) { + LeftIndex leftIndex = new LeftIndex(indexValues, indexField); + if (this.leftIndexMap.containsKey(elementId)) { + this.leftIndexMap.get(elementId).add(leftIndex); + } else { + this.leftIndexMap.put(elementId, Sets.newHashSet(leftIndex)); + } + } + + public Set getLeftIndex(Id elementId) { + return this.leftIndexMap.get(elementId); + } + + public void removeElementLeftIndex(Id elementId) { + this.leftIndexMap.remove(elementId); + } + + public boolean validRangeIndex(HugeElement element, Condition cond) { + // Not UserpropRelation + if (!(cond instanceof Condition.UserpropRelation)) { + return true; + } + + Condition.UserpropRelation propRelation = + (Condition.UserpropRelation) cond; + Id propId = propRelation.key(); + Set fieldValues = this.removeIndexValues(propId, + element.id()); + if (fieldValues == null) { + // Not range index + return true; + } + + HugeProperty hugeProperty = element.getProperty(propId); + if (hugeProperty == null) { + // Property value has been deleted + this.addLeftIndex(propId, fieldValues, element.id()); + return false; + } + + /* + * NOTE: If success remove means has correct index, + * we should add left index values to left index map + * waiting to be removed + */ + boolean hasRightValue = removeValue(fieldValues, hugeProperty.value()); + if (fieldValues.size() > 0) { + this.addLeftIndex(propId, fieldValues, element.id()); + } + + /* + * NOTE: When query by more than one range index field, + * if current field is not the selected one, it can only be used to + * determine whether the index values matched, can't determine + * the element is valid or not + */ + if (this.selectedIndexField != null) { + return !propId.equals(this.selectedIndexField) || hasRightValue; + } + + return hasRightValue; + } + + private static boolean removeValue(Set values, Object value){ + for (Object compareValue : values) { + if (numberEquals(compareValue, value)) { + values.remove(compareValue); + return true; + } + } + return false; + } + + private static boolean numberEquals(Object number1, Object number2) { + // Same class compare directly + if (number1.getClass().equals(number2.getClass())) { + return number1.equals(number2); + } + + // Otherwise convert to BigDecimal to make two numbers comparable + Number n1 = NumericUtil.convertToNumber(number1); + Number n2 = NumericUtil.convertToNumber(number2); + BigDecimal b1 = new BigDecimal(n1.doubleValue()); + BigDecimal b2 = new BigDecimal(n2.doubleValue()); + return b1.compareTo(b2) == 0; + } + } + + public static final class LeftIndex { + + private final Set indexFieldValues; + private final Id indexField; + + public LeftIndex(Set indexFieldValues, Id indexField) { + this.indexFieldValues = indexFieldValues; + this.indexField = indexField; + } + + public Set indexFieldValues() { + return indexFieldValues; + } + + public Id indexField() { + return indexField; + } + } } 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 c76c4beadb..b55ebcbc3b 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 @@ -120,6 +120,7 @@ public GraphIndexTransaction(HugeGraphParams graph, BackendStore store) { protected Id asyncRemoveIndexLeft(ConditionQuery query, HugeElement element) { + LOG.info("Remove left index: {}, query: {}", element, query); RemoveLeftIndexJob job = new RemoveLeftIndexJob(query, element); HugeTask task = EphemeralJobBuilder.of(this.graph()) .name(element.id().asString()) @@ -589,6 +590,7 @@ private IdHolder doJointIndex(IndexQueries queries) { IdHolder holder = this.doIndexQuery(indexLabel, query); if (resultHolder == null) { resultHolder = holder; + this.storeSelectedIndexField(indexLabel, query); } assert this.indexIntersectThresh > 0; // default value is 1000 Set ids = ((BatchIdHolder) holder).peekNext( @@ -600,6 +602,7 @@ private IdHolder doJointIndex(IndexQueries queries) { } else if (filtering) { assert ids.size() < this.indexIntersectThresh; resultHolder = holder; + this.storeSelectedIndexField(indexLabel, query); break; } else { if (intersectIds == null) { @@ -621,6 +624,20 @@ private IdHolder doJointIndex(IndexQueries queries) { } } + private void storeSelectedIndexField(IndexLabel indexLabel, + ConditionQuery query) { + // Only store range index field + if (!indexLabel.indexType().isRange()) { + return; + } + + ConditionQuery originConditionQuery = + query.originConditionQuery(); + if (originConditionQuery != null) { + originConditionQuery.selectedIndexField(indexLabel.indexField()); + } + } + @Watched(prefix = "index") private IdHolder doIndexQuery(IndexLabel indexLabel, ConditionQuery query) { if (!query.paging()) { @@ -660,6 +677,7 @@ private IdHolder doIndexQueryBatch(IndexLabel indexLabel, this.removeExpiredIndexIfNeeded(index, query.showExpired()); ids.addAll(index.elementIds()); Query.checkForceCapacity(ids.size()); + this.recordIndexValue(query, index); } return ids; } finally { @@ -668,6 +686,19 @@ private IdHolder doIndexQueryBatch(IndexLabel indexLabel, }); } + private void recordIndexValue(ConditionQuery query, HugeIndex index) { + if (!shouldRecordIndexValue(query, index)) { + return; + } + + ConditionQuery originQuery = query.originConditionQuery(); + Id fieldId = index.indexLabel().indexField(); + for (Id id : index.elementIds()) { + Object value = index.indexLabel().validValue(index.fieldValues()); + originQuery.recordIndexValue(fieldId, id, value); + } + } + @Watched(prefix = "index") private PageIds doIndexQueryOnce(IndexLabel indexLabel, ConditionQuery query) { @@ -689,6 +720,7 @@ private PageIds doIndexQueryOnce(IndexLabel indexLabel, break; } Query.checkForceCapacity(ids.size()); + this.recordIndexValue(query, index); } // If there is no data, the entries is not a Metadatable object if (ids.isEmpty()) { @@ -1093,6 +1125,13 @@ private static boolean cmn(List all, int m, int n, return false; } + private static boolean shouldRecordIndexValue(ConditionQuery query, + HugeIndex index) { + // Currently, only range index has problems + return query.originQuery() instanceof ConditionQuery && + index.indexLabel().indexType().isRange(); + } + private static IndexQueries constructJointSecondaryQueries( ConditionQuery query, List ils) { @@ -1656,6 +1695,7 @@ public static class RemoveLeftIndexJob extends EphemeralJob { private final ConditionQuery query; private final HugeElement element; private GraphIndexTransaction tx; + private Set leftIndexes; private RemoveLeftIndexJob(ConditionQuery query, HugeElement element) { E.checkArgumentNotNull(query, "query"); @@ -1663,6 +1703,7 @@ private RemoveLeftIndexJob(ConditionQuery query, HugeElement element) { this.query = query; this.element = element; this.tx = null; + this.leftIndexes = query.getElementLeftIndex(element.id()); } @Override @@ -1716,53 +1757,44 @@ protected long removeIndexLeft(ConditionQuery query, private long processRangeIndexLeft(ConditionQuery query, HugeElement element) { - GraphIndexTransaction tx = this.tx; - AbstractSerializer serializer = tx.serializer; long count = 0; - // Construct index ConditionQuery - Set matchedIndexes = tx.collectMatchedIndexes(query); - IndexQueries queries = null; - Id elementLabelId = element.schemaLabel().id(); - for (MatchedIndex index : matchedIndexes) { - if (index.schemaLabel().id().equals(elementLabelId)) { - queries = index.constructIndexQueries(query); - break; + if (this.leftIndexes == null) { + return count; + } + + for (ConditionQuery.LeftIndex leftIndex : this.leftIndexes) { + Set indexValues = leftIndex.indexFieldValues(); + IndexLabel indexLabel = this.findMatchedIndexLabel(query, + leftIndex); + assert indexLabel != null; + + AbstractSerializer serializer = this.tx.serializer; + for (Object value : indexValues) { + HugeIndex index = new HugeIndex(this.graph(), indexLabel); + index.elementIds(element.id()); + index.fieldValues(value); + this.tx.doEliminate(serializer.writeIndex(index)); + count++; } } + // Remove LeftIndex after constructing remove job + this.query.removeElementLeftIndex(element.id()); + this.tx.commit(); + return count; + } - E.checkState(queries != null, - "Can't construct left-index query for '%s'", query); - - for (ConditionQuery q : queries.values()) { - if (!q.resultType().isRangeIndex()) { - continue; - } - // Query and delete index equals element id - Iterator it = tx.query(q).iterator(); - try { - while (it.hasNext()) { - HugeIndex index = serializer.readIndex(graph(), q, - it.next()); - tx.removeExpiredIndexIfNeeded(index, q.showExpired()); - if (index.elementIds().contains(element.id())) { - index.resetElementIds(); - index.elementIds(element.id()); - tx.doEliminate(serializer.writeIndex(index)); - tx.commit(); - // If deleted by error, re-add deleted index again - if (this.deletedByError(query, element)) { - tx.doAppend(serializer.writeIndex(index)); - tx.commit(); - } else { - count++; - } - } - } - } finally { - CloseableIterator.closeIterator(it); - } + private IndexLabel findMatchedIndexLabel(ConditionQuery query, + ConditionQuery.LeftIndex + leftIndex) { + Set matchedIndexes = this.tx.collectMatchedIndexes(query); + for (MatchedIndex index : matchedIndexes) { + for (IndexLabel label : index.indexLabels()){ + if (label.indexField().equals(leftIndex.indexField())){ + return label; + } + } } - return count; + return null; } private long processSecondaryOrSearchIndexLeft(ConditionQuery query, diff --git a/hugegraph-core/src/main/java/com/baidu/hugegraph/backend/tx/GraphTransaction.java b/hugegraph-core/src/main/java/com/baidu/hugegraph/backend/tx/GraphTransaction.java index 67fe318fb4..db019e60e9 100644 --- a/hugegraph-core/src/main/java/com/baidu/hugegraph/backend/tx/GraphTransaction.java +++ b/hugegraph-core/src/main/java/com/baidu/hugegraph/backend/tx/GraphTransaction.java @@ -125,6 +125,7 @@ public class GraphTransaction extends IndexableTransaction { private final boolean checkCustomVertexExist; private final boolean checkAdjacentVertexExist; private final boolean lazyLoadAdjacentVertex; + private final boolean removeLeftIndexOnOverwrite; private final boolean ignoreInvalidEntry; private final int commitPartOfAdjacentEdges; private final int batchSize; @@ -148,6 +149,8 @@ public GraphTransaction(HugeGraphParams graph, BackendStore store) { conf.get(CoreOptions.VERTEX_ADJACENT_VERTEX_EXIST); this.lazyLoadAdjacentVertex = conf.get(CoreOptions.VERTEX_ADJACENT_VERTEX_LAZY); + this.removeLeftIndexOnOverwrite = + conf.get(CoreOptions.VERTEX_REMOVE_LEFT_INDEX); this.commitPartOfAdjacentEdges = conf.get(CoreOptions.VERTEX_PART_EDGE_COMMIT_SIZE); this.ignoreInvalidEntry = @@ -316,6 +319,11 @@ protected void prepareAdditions(Map addedVertices, if (this.checkCustomVertexExist) { this.checkVertexExistIfCustomizedId(addedVertices); } + + if (this.removeLeftIndexOnOverwrite) { + this.removeLeftIndexIfNeeded(addedVertices); + } + // Do vertex update for (HugeVertex v : addedVertices.values()) { assert !v.removed(); @@ -1600,6 +1608,23 @@ private void lockForUpdateProperty(SchemaLabel schemaLabel, } } + private void removeLeftIndexIfNeeded(Map vertices) { + Set ids = vertices.keySet(); + if (ids.isEmpty()) { + return; + } + IdQuery idQuery = new IdQuery(HugeType.VERTEX, ids); + Iterator results = this.queryVerticesFromBackend(idQuery); + try { + while (results.hasNext()) { + HugeVertex existedVertex = results.next(); + this.indexTx.updateVertexIndex(existedVertex, true); + } + } finally { + CloseableIterator.closeIterator(results); + } + } + private Iterator filterUnmatchedRecords( Iterator results, Query query) { @@ -1645,6 +1670,14 @@ private boolean rightResultFromIndexQuery(Query query, HugeElement elem) { ConditionQuery cq = (ConditionQuery) query; if (cq.optimized() == OptimizedType.NONE || cq.test(elem)) { + if (cq.existLeftIndex(elem.id())) { + /* + * Both have correct and left index, wo should return true + * but also needs to cleaned up left index + */ + this.indexTx.asyncRemoveIndexLeft(cq, elem); + } + /* Return true if: * 1.not query by index or by primary-key/sort-key * (cq.optimized() == 0 means query just by sysprop) @@ -1654,7 +1687,6 @@ private boolean rightResultFromIndexQuery(Query query, HugeElement elem) { } if (cq.optimized() == OptimizedType.INDEX) { - LOG.info("Remove left index: {}, query: {}", elem, cq); this.indexTx.asyncRemoveIndexLeft(cq, elem); } return false; diff --git a/hugegraph-core/src/main/java/com/baidu/hugegraph/config/CoreOptions.java b/hugegraph-core/src/main/java/com/baidu/hugegraph/config/CoreOptions.java index e4a65c608f..07e30d4c36 100644 --- a/hugegraph-core/src/main/java/com/baidu/hugegraph/config/CoreOptions.java +++ b/hugegraph-core/src/main/java/com/baidu/hugegraph/config/CoreOptions.java @@ -352,6 +352,14 @@ public static synchronized CoreOptions instance() { false ); + public static final ConfigOption VERTEX_REMOVE_LEFT_INDEX = + new ConfigOption<>( + "vertex.remove_left_index_at_overwrite", + "Whether remove left index at overwrite.", + disallowEmpty(), + false + ); + public static final ConfigOption VERTEX_ADJACENT_VERTEX_EXIST = new ConfigOption<>( "vertex.check_adjacent_vertex_exist", 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 ec00cda0c0..bd98c70927 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 @@ -134,6 +134,26 @@ public boolean olap() { return OLAP_ID.equals(this.baseValue); } + public Object validValue(Object value) { + if (!(value instanceof Number)) { + return value; + } + + Number number = (Number) value; + switch (this.indexType()) { + case RANGE_INT: + return number.intValue(); + case RANGE_LONG: + return number.longValue(); + case RANGE_FLOAT: + return number.floatValue(); + case RANGE_DOUBLE: + return number.doubleValue(); + default: + return value; + } + } + // ABS of System index id must be below SchemaElement.MAX_PRIMITIVE_SYS_ID private static final int VL_IL_ID = -1; private static final int EL_IL_ID = -2; 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 0c6409ade4..aefd900eb2 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 @@ -413,6 +413,179 @@ public void testAddVertexWithInvalidPropertyList() { }); } + @Test + public void testRemoveLeftRangeIndex() throws InterruptedException { + HugeGraph graph = graph(); + SchemaManager schema = graph.schema(); + + schema.propertyKey("updateTime").asLong().create(); + + schema.vertexLabel("soft").properties("name", "updateTime") + .primaryKeys("name").create(); + + schema.indexLabel("softByUpdateTime").onV("soft").range() + .by("updateTime").create(); + + final long testDataCount = 10L; + + for (int i = 1; i <= testDataCount; i++) { + graph.addVertex(T.label, "soft", "name", "soft" + i, + "updateTime", i); + } + graph.tx().commit(); + + long count = graph.traversal().V() + .has("soft", "updateTime", + ConditionP.gt(0)) + .limit(-1) + .count() + .next(); + Assert.assertEquals(testDataCount, count); + + for (int i = 1; i <= testDataCount; i++) { + graph.addVertex(T.label, "soft", "name", "soft" + i, + "updateTime", 2 * i); + } + graph.tx().commit(); + + count = graph.traversal().V() + .has("soft", "updateTime", + ConditionP.gt(0)) + .limit(-1) + .count() + .next(); + Assert.assertEquals(testDataCount, count); + + schema.propertyKey("score").asInt().create(); + schema.vertexLabel("developer").properties("name", "age", "score") + .primaryKeys("name").create(); + + schema.indexLabel("developerByAge").onV("developer").range() + .by("age").create(); + schema.indexLabel("developerByScore").onV("developer").range() + .by("score").create(); + + for (int i = 1; i <= testDataCount; i++) { + graph.addVertex(T.label, "developer", "name", "developer" + i, + "age", i, + "score", i); + } + graph.tx().commit(); + + count = graph.traversal().V() + .hasLabel("developer") + .has("age", ConditionP.gt(0)) + .has("score", ConditionP.gt(0)) + .limit(-1) + .count() + .next(); + Assert.assertEquals(testDataCount, count); + + for (int i = 1; i <= testDataCount; i++) { + graph.addVertex(T.label, "developer", "name", "developer" + i, + "age", i * 2, + "score", i * 2); + } + graph.tx().commit(); + + count = graph.traversal().V() + .hasLabel("developer") + .has("age", ConditionP.gt(0)) + .has("score", ConditionP.gt(0)) + .limit(-1) + .count() + .next(); + + Assert.assertEquals(testDataCount, count); + + // Wait left index to be removed + Thread.sleep(2000); + + count = graph.traversal().V() + .hasLabel("developer") + .has("age", ConditionP.gt(0)) + .limit(-1) + .count() + .next(); + // Debug stop here to see whether the left index be correctly determined + Assert.assertEquals(testDataCount, count); + + // mock test removeLeftIndexIfNeeded + boolean removeLeftIndexOnOverwrite = + Whitebox.getInternalState(params().graphTransaction(), + "removeLeftIndexOnOverwrite"); + Whitebox.setInternalState(params().graphTransaction(), + "removeLeftIndexOnOverwrite", true); + + for (int i = 1; i <= testDataCount; i++) { + graph.addVertex(T.label, "developer", "name", "developer" + i, + "age", i * 3, + "score", i * 3); + } + graph.tx().commit(); + + if (!removeLeftIndexOnOverwrite) { + Whitebox.setInternalState(params().graphTransaction(), + "removeLeftIndexOnOverwrite", false); + } + + count = graph.traversal().V() + .hasLabel("developer") + .has("age", ConditionP.gt(0)) + .has("score", ConditionP.gt(0)) + .limit(-1) + .count() + .next(); + Assert.assertEquals(testDataCount, count); + } + + @Test + public void testLeftUnionIndex(){ + HugeGraph graph = graph(); + SchemaManager schema = graph.schema(); + + schema.propertyKey("updateTime").asLong().create(); + schema.propertyKey("country").asText().create(); + + schema.vertexLabel("soft").properties("name", "country", "updateTime") + .primaryKeys("name").create(); + + schema.indexLabel("softByCountryAndUpdateTime").onV("soft").secondary() + .by("country", "updateTime").create(); + + final long testDataCount = 2L; + + for (int i = 1; i <= testDataCount; i++) { + graph.addVertex(T.label, "soft", "name", "soft" + i, + "country", "china", "updateTime", i); + } + graph.tx().commit(); + + long count = graph.traversal().V() + .hasLabel("soft") + .has("updateTime", 2L) + .has("country", "china") + .limit(-1) + .count() + .next(); + Assert.assertEquals(1, count); + + for (int i = 1; i <= testDataCount; i++) { + graph.addVertex(T.label, "soft", "name", "soft" + i, + "country", "china", "updateTime", i * 2); + } + graph.tx().commit(); + + count = graph.traversal().V() + .hasLabel("soft") + .has("updateTime", 2L) + .has("country", "china") + .limit(-1) + .count() + .next(); + Assert.assertEquals(1L, count); + } + @Test public void testAddVertexWithPropertySet() { HugeGraph graph = graph();