diff --git a/docs/basics.adoc b/docs/basics.adoc index f9d98f2f9cc..e97f792fb26 100644 --- a/docs/basics.adoc +++ b/docs/basics.adoc @@ -995,6 +995,9 @@ While the index definition example looks similar to the composite index above, i g.V().has('name', textContains('hercules')).has('age', inside(20, 50)) g.V().has('name', textContains('hercules')) g.V().has('age', lt(50)) +g.V().has('age', outside(20, 50)) +g.V().has('age', lt(50).or(gte(60))) +g.V().or(__.has('name', textContains('hercules')), __.has('age', inside(20, 50))) Mixed indexes support full-text search, range search, geo search and others. Refer to <> for a list of predicates supported by a particular indexing backend. diff --git a/docs/elasticsearch.adoc b/docs/elasticsearch.adoc index afd610faaaa..4a74ee9db72 100644 --- a/docs/elasticsearch.adoc +++ b/docs/elasticsearch.adoc @@ -13,7 +13,8 @@ JanusGraph supports https://www.elastic.co/[Elasticsearch] as an index backend. * *TTL*: Supports automatically expiring indexed elements. * *Collections*: Supports indexing SET and LIST cardinality properties. * *Temporal*: Nanosecond granularity temporal indexing. -* *Custom Analyzer*: Choose to use a custom analyzer +* *Custom Analyzer*: Choose to use a custom analyzer. +* *Not Query-normal-form*: Supports others queries than Query-normal-form (QNF). QNF for JanusGraph is a variant of CNF (conjunctive normal form) with negation inlined where possible. Please see <> for details on what versions of Elasticsearch will work with JanusGraph. diff --git a/docs/lucene.adoc b/docs/lucene.adoc index 9de159bb414..fe6627b48fc 100644 --- a/docs/lucene.adoc +++ b/docs/lucene.adoc @@ -24,6 +24,7 @@ In the above configuration, the index backend is named `search`. Replace `search * *Geo*: Supports `Geo` predicates to search for geo properties that are intersecting, within, or contained in a given query geometry. Supports points, lines and polygons for indexing. Supports circles and boxes for querying point properties and all shapes for querying non-point properties. Note that JTS is required when using line and polygon shapes (see <> for more information). * *Numeric Range*: Supports all numeric comparisons in `Compare`. * *Temporal*: Nanosecond granularity temporal indexing. +* *Not Query-normal-form*: Supports other query than Query-normal-form (QNF). QNF for JanusGraph is a variant of CNF (conjunctive normal form) with negation inlined where possible. === Configuration Options diff --git a/janusgraph-core/src/main/java/org/janusgraph/core/JanusGraphQuery.java b/janusgraph-core/src/main/java/org/janusgraph/core/JanusGraphQuery.java index 7546d6072d4..ba315edb6e1 100644 --- a/janusgraph-core/src/main/java/org/janusgraph/core/JanusGraphQuery.java +++ b/janusgraph-core/src/main/java/org/janusgraph/core/JanusGraphQuery.java @@ -55,6 +55,8 @@ public interface JanusGraphQuery> { Q hasNot(String key, Object value); + Q or(Q subQuery); + > Q interval(String key, T startValue, T endValue); /** diff --git a/janusgraph-core/src/main/java/org/janusgraph/diskstorage/indexing/IndexFeatures.java b/janusgraph-core/src/main/java/org/janusgraph/diskstorage/indexing/IndexFeatures.java index 6da24dbbef6..3364feb5f74 100644 --- a/janusgraph-core/src/main/java/org/janusgraph/diskstorage/indexing/IndexFeatures.java +++ b/janusgraph-core/src/main/java/org/janusgraph/diskstorage/indexing/IndexFeatures.java @@ -37,11 +37,12 @@ public class IndexFeatures { private final boolean supportsNanoseconds; private final boolean supportsCustomAnalyzer; private final boolean supportsGeoContains; + private final boolean supportNotQueryNormalForm; private final ImmutableSet supportedCardinalities; public IndexFeatures(boolean supportsDocumentTTL, Mapping defaultMap, ImmutableSet supportedMap, String wildcardField, ImmutableSet supportedCardinalities, boolean supportsNanoseconds, - boolean supportCustomAnalyzer, boolean supportsGeoContains) { + boolean supportCustomAnalyzer, boolean supportsGeoContains, boolean supportNotQueryNormalForm) { Preconditions.checkArgument(defaultMap!=null && defaultMap!=Mapping.DEFAULT); Preconditions.checkArgument(supportedMap!=null && !supportedMap.isEmpty() @@ -54,6 +55,7 @@ public IndexFeatures(boolean supportsDocumentTTL, Mapping defaultMap, ImmutableS this.supportsNanoseconds = supportsNanoseconds; this.supportsCustomAnalyzer = supportCustomAnalyzer; this.supportsGeoContains = supportsGeoContains; + this.supportNotQueryNormalForm = supportNotQueryNormalForm; } public boolean supportsDocumentTTL() { @@ -79,7 +81,7 @@ public boolean supportsCardinality(Cardinality cardinality) { public boolean supportsNanoseconds() { return supportsNanoseconds; } - + public boolean supportsCustomAnalyzer() { return supportsCustomAnalyzer; } @@ -88,6 +90,10 @@ public boolean supportsGeoContains() { return supportsGeoContains; } + public boolean supportNotQueryNormalForm() { + return supportNotQueryNormalForm; + } + public static class Builder { private boolean supportsDocumentTTL = false; @@ -97,7 +103,8 @@ public static class Builder { private String wildcardField = "*"; private boolean supportsNanoseconds; private boolean supportsCustomAnalyzer; - private boolean supportsGeoContains = false; + private boolean supportsGeoContains; + private boolean supportNotQueryNormalForm; public Builder supportsDocumentTTL() { supportsDocumentTTL=true; @@ -128,7 +135,7 @@ public Builder supportsNanoseconds() { supportsNanoseconds = true; return this; } - + public Builder supportsCustomAnalyzer() { supportsCustomAnalyzer = true; return this; @@ -139,13 +146,15 @@ public Builder supportsGeoContains() { return this; } + public Builder supportNotQueryNormalForm() { + this.supportNotQueryNormalForm = true; + return this; + } + public IndexFeatures build() { return new IndexFeatures(supportsDocumentTTL, defaultStringMapping, ImmutableSet.copyOf(supportedMappings), wildcardField, ImmutableSet.copyOf(supportedCardinalities), supportsNanoseconds, supportsCustomAnalyzer, - supportsGeoContains); + supportsGeoContains, supportNotQueryNormalForm); } - - } - } diff --git a/janusgraph-core/src/main/java/org/janusgraph/diskstorage/indexing/IndexQuery.java b/janusgraph-core/src/main/java/org/janusgraph/diskstorage/indexing/IndexQuery.java index 074fd5095a1..debe617847a 100644 --- a/janusgraph-core/src/main/java/org/janusgraph/diskstorage/indexing/IndexQuery.java +++ b/janusgraph-core/src/main/java/org/janusgraph/diskstorage/indexing/IndexQuery.java @@ -51,7 +51,6 @@ public IndexQuery(String store, Condition condition, ImmutableList o Preconditions.checkNotNull(store); Preconditions.checkNotNull(condition); Preconditions.checkArgument(orders != null); - Preconditions.checkArgument(QueryUtil.isQueryNormalForm(condition)); this.condition = condition; this.orders = orders; this.store = store; @@ -103,14 +102,14 @@ public boolean equals(Object other) { if (this == other) return true; else if (other == null) return false; else if (!getClass().isInstance(other)) return false; - IndexQuery oth = (IndexQuery) other; + final IndexQuery oth = (IndexQuery) other; return store.equals(oth.store) && orders.equals(oth.orders) && condition.equals(oth.condition) && getLimit() == oth.getLimit(); } @Override public String toString() { - StringBuilder b = new StringBuilder(); + final StringBuilder b = new StringBuilder(); b.append("[").append(condition.toString()).append("]"); if (!orders.isEmpty()) b.append(orders); if (hasLimit()) b.append("(").append(getLimit()).append(")"); @@ -155,7 +154,7 @@ public boolean equals(Object oth) { if (this == oth) return true; else if (oth == null) return false; else if (!getClass().isInstance(oth)) return false; - OrderEntry o = (OrderEntry) oth; + final OrderEntry o = (OrderEntry) oth; return key.equals(o.key) && order == o.order; } diff --git a/janusgraph-core/src/main/java/org/janusgraph/graphdb/database/IndexSerializer.java b/janusgraph-core/src/main/java/org/janusgraph/graphdb/database/IndexSerializer.java index 2f925c80219..f3e3fd87035 100644 --- a/janusgraph-core/src/main/java/org/janusgraph/graphdb/database/IndexSerializer.java +++ b/janusgraph-core/src/main/java/org/janusgraph/graphdb/database/IndexSerializer.java @@ -95,7 +95,7 @@ public boolean containsIndex(final String indexName) { public String getDefaultFieldName(final PropertyKey key, final Parameter[] parameters, final String indexName) { Preconditions.checkArgument(!ParameterType.MAPPED_NAME.hasParameter(parameters),"A field name mapping has been specified for key: %s",key); Preconditions.checkArgument(containsIndex(indexName),"Unknown backing index: %s",indexName); - String fieldname = configuration.get(INDEX_NAME_MAPPING,indexName)?key.name():keyID2Name(key); + final String fieldname = configuration.get(INDEX_NAME_MAPPING,indexName)?key.name():keyID2Name(key); return mixedIndexes.get(indexName).mapKey2Field(fieldname, new StandardKeyInformation(key,parameters)); } @@ -105,24 +105,24 @@ public static void register(final MixedIndexType index, final PropertyKey key, f } -// public boolean supports(final String indexName, final Class dataType, final Parameter[] parameters) { -// IndexInformation indexinfo = indexes.get(indexName); -// Preconditions.checkArgument(indexinfo != null, "Index is unknown or not configured: %s", indexName); -// return indexinfo.supports(new StandardKeyInformation(dataType,parameters)); -// } - public boolean supports(final MixedIndexType index, final ParameterIndexField field) { - IndexInformation indexinfo = mixedIndexes.get(index.getBackingIndexName()); + final IndexInformation indexinfo = mixedIndexes.get(index.getBackingIndexName()); Preconditions.checkArgument(indexinfo != null, "Index is unknown or not configured: %s", index.getBackingIndexName()); return indexinfo.supports(getKeyInformation(field)); } public boolean supports(final MixedIndexType index, final ParameterIndexField field, final JanusGraphPredicate predicate) { - IndexInformation indexinfo = mixedIndexes.get(index.getBackingIndexName()); + final IndexInformation indexinfo = mixedIndexes.get(index.getBackingIndexName()); Preconditions.checkArgument(indexinfo != null, "Index is unknown or not configured: %s", index.getBackingIndexName()); return indexinfo.supports(getKeyInformation(field),predicate); } + public IndexFeatures features(final MixedIndexType index) { + final IndexInformation indexinfo = mixedIndexes.get(index.getBackingIndexName()); + Preconditions.checkArgument(indexinfo != null, "Index is unknown or not configured: %s", index.getBackingIndexName()); + return indexinfo.getFeatures(); + } + private static StandardKeyInformation getKeyInformation(final ParameterIndexField field) { return new StandardKeyInformation(field.getFieldKey(),field.getParameters()); } @@ -157,8 +157,8 @@ public KeyInformation.StoreRetriever get(final String store) { Preconditions.checkState(transaction!=null,"Retriever has not been initialized"); final MixedIndexType extIndex = getMixedIndex(store, transaction); assert extIndex.getBackingIndexName().equals(index); - ImmutableMap.Builder b = ImmutableMap.builder(); - for (ParameterIndexField field : extIndex.getFieldKeys()) b.put(key2Field(field),getKeyInformation(field)); + final ImmutableMap.Builder b = ImmutableMap.builder(); + for (final ParameterIndexField field : extIndex.getFieldKeys()) b.put(key2Field(field),getKeyInformation(field)); final ImmutableMap infoMap = b.build(); final KeyInformation.StoreRetriever storeRetriever = infoMap::get; indexes.put(store,storeRetriever); @@ -245,7 +245,7 @@ public int hashCode() { public boolean equals(Object other) { if (this==other) return true; else if (other==null || !(other instanceof IndexUpdate)) return false; - IndexUpdate oth = (IndexUpdate)other; + final IndexUpdate oth = (IndexUpdate)other; return index.equals(oth.index) && mutationType==oth.mutationType && key.equals(oth.key) && entry.equals(oth.entry); } } @@ -264,18 +264,18 @@ private static boolean indexAppliesTo(IndexType index, JanusGraphElement element public Collection getIndexUpdates(InternalRelation relation) { assert relation.isNew() || relation.isRemoved(); - Set updates = Sets.newHashSet(); - IndexUpdate.Type updateType = getUpdateType(relation); - int ttl = updateType==IndexUpdate.Type.ADD?StandardJanusGraph.getTTL(relation):0; - for (RelationType type : relation.getPropertyKeysDirect()) { + final Set updates = Sets.newHashSet(); + final IndexUpdate.Type updateType = getUpdateType(relation); + final int ttl = updateType==IndexUpdate.Type.ADD?StandardJanusGraph.getTTL(relation):0; + for (final RelationType type : relation.getPropertyKeysDirect()) { if (!(type instanceof PropertyKey)) continue; - PropertyKey key = (PropertyKey)type; - for (IndexType index : ((InternalRelationType)key).getKeyIndexes()) { + final PropertyKey key = (PropertyKey)type; + for (final IndexType index : ((InternalRelationType)key).getKeyIndexes()) { if (!indexAppliesTo(index,relation)) continue; IndexUpdate update; if (index instanceof CompositeIndexType) { - CompositeIndexType iIndex= (CompositeIndexType) index; - RecordEntry[] record = indexMatch(relation, iIndex); + final CompositeIndexType iIndex= (CompositeIndexType) index; + final RecordEntry[] record = indexMatch(relation, iIndex); if (record==null) continue; update = new IndexUpdate<>(iIndex, updateType, getIndexKey(iIndex, record), getIndexEntry(iIndex, record, relation), relation); } else { @@ -291,15 +291,15 @@ public Collection getIndexUpdates(InternalRelation relation) { } private static PropertyKey[] getKeysOfRecords(RecordEntry[] record) { - PropertyKey[] keys = new PropertyKey[record.length]; + final PropertyKey[] keys = new PropertyKey[record.length]; for (int i=0;i 0 && (kttl < ttl || ttl <= 0)) ttl = kttl; } return ttl; @@ -307,28 +307,28 @@ private static int getIndexTTL(InternalVertex vertex, PropertyKey... keys) { public Collection getIndexUpdates(InternalVertex vertex, Collection updatedProperties) { if (updatedProperties.isEmpty()) return Collections.emptyList(); - Set updates = Sets.newHashSet(); + final Set updates = Sets.newHashSet(); - for (InternalRelation rel : updatedProperties) { + for (final InternalRelation rel : updatedProperties) { assert rel.isProperty(); - JanusGraphVertexProperty p = (JanusGraphVertexProperty)rel; + final JanusGraphVertexProperty p = (JanusGraphVertexProperty)rel; assert rel.isNew() || rel.isRemoved(); assert rel.getVertex(0).equals(vertex); - IndexUpdate.Type updateType = getUpdateType(rel); - for (IndexType index : ((InternalRelationType)p.propertyKey()).getKeyIndexes()) { + final IndexUpdate.Type updateType = getUpdateType(rel); + for (final IndexType index : ((InternalRelationType)p.propertyKey()).getKeyIndexes()) { if (!indexAppliesTo(index,vertex)) continue; if (index.isCompositeIndex()) { //Gather composite indexes - CompositeIndexType cIndex = (CompositeIndexType)index; - IndexRecords updateRecords = indexMatches(vertex,cIndex,updateType==IndexUpdate.Type.DELETE,p.propertyKey(),new RecordEntry(p)); - for (RecordEntry[] record : updateRecords) { + final CompositeIndexType cIndex = (CompositeIndexType)index; + final IndexRecords updateRecords = indexMatches(vertex,cIndex,updateType==IndexUpdate.Type.DELETE,p.propertyKey(),new RecordEntry(p)); + for (final RecordEntry[] record : updateRecords) { final IndexUpdate update = new IndexUpdate<>(cIndex, updateType, getIndexKey(cIndex, record), getIndexEntry(cIndex, record, vertex), vertex); - int ttl = getIndexTTL(vertex,getKeysOfRecords(record)); + final int ttl = getIndexTTL(vertex,getKeysOfRecords(record)); if (ttl>0 && updateType== IndexUpdate.Type.ADD) update.setTTL(ttl); updates.add(update); } } else { //Update mixed indexes if (((MixedIndexType)index).getField(p.propertyKey()).getStatus()== SchemaStatus.DISABLED) continue; final IndexUpdate update = getMixedIndexUpdate(vertex, p.propertyKey(), p.value(), (MixedIndexType) index, updateType); - int ttl = getIndexTTL(vertex,p.propertyKey()); + final int ttl = getIndexTTL(vertex,p.propertyKey()); if (ttl>0 && updateType== IndexUpdate.Type.ADD) update.setTTL(ttl); updates.add(update); } @@ -344,9 +344,9 @@ private IndexUpdate getMixedIndexUpdate(JanusGraphElement ele public void reindexElement(JanusGraphElement element, MixedIndexType index, Map>> documentsPerStore) { if (!indexAppliesTo(index,element)) return; - List entries = Lists.newArrayList(); - for (ParameterIndexField field: index.getFieldKeys()) { - PropertyKey key = field.getFieldKey(); + final List entries = Lists.newArrayList(); + for (final ParameterIndexField field: index.getFieldKeys()) { + final PropertyKey key = field.getFieldKey(); if (field.getStatus()==SchemaStatus.DISABLED) continue; if (element.properties(key.name()).hasNext()) { element.values(key.name()).forEachRemaining(value->entries.add(new IndexEntry(key2Field(field), value))); @@ -367,28 +367,28 @@ public void removeElement(Object elementId, MixedIndexType index, Map> reindexElement(JanusGraphElement element, CompositeIndexType index) { - Set> indexEntries = Sets.newHashSet(); + final Set> indexEntries = Sets.newHashSet(); if (!indexAppliesTo(index,element)) return indexEntries; Iterable records; if (element instanceof JanusGraphVertex) records = indexMatches((JanusGraphVertex)element,index); else { assert element instanceof JanusGraphRelation; records = Collections.EMPTY_LIST; - RecordEntry[] record = indexMatch((JanusGraphRelation)element,index); + final RecordEntry[] record = indexMatch((JanusGraphRelation)element,index); if (record!=null) records = ImmutableList.of(record); } - for (RecordEntry[] record : records) { + for (final RecordEntry[] record : records) { indexEntries.add(new IndexUpdate<>(index, IndexUpdate.Type.ADD, getIndexKey(index, record), getIndexEntry(index, record, element), element)); } return indexEntries; } public static RecordEntry[] indexMatch(JanusGraphRelation relation, CompositeIndexType index) { - IndexField[] fields = index.getFieldKeys(); - RecordEntry[] match = new RecordEntry[fields.length]; + final IndexField[] fields = index.getFieldKeys(); + final RecordEntry[] match = new RecordEntry[fields.length]; for (int i = 0; i { + @Override public boolean add(RecordEntry[] record) { return super.add(Arrays.copyOf(record,record.length)); } @@ -412,7 +413,7 @@ public Object[] apply(final RecordEntry[] record) { } private static Object[] getValues(RecordEntry[] record) { - Object[] values = new Object[record.length]; + final Object[] values = new Object[record.length]; for (int i = 0; i < values.length; i++) { values[i]=record[i].value; } @@ -444,8 +445,8 @@ public static IndexRecords indexMatches(JanusGraphVertex vertex, CompositeIndexT public static IndexRecords indexMatches(JanusGraphVertex vertex, CompositeIndexType index, PropertyKey replaceKey, Object replaceValue) { - IndexRecords matches = new IndexRecords(); - IndexField[] fields = index.getFieldKeys(); + final IndexRecords matches = new IndexRecords(); + final IndexField[] fields = index.getFieldKeys(); if (indexAppliesTo(index,vertex)) { indexMatches(vertex,new RecordEntry[fields.length],matches,fields,0,false, replaceKey,new RecordEntry(0,replaceValue,replaceKey)); @@ -455,8 +456,8 @@ public static IndexRecords indexMatches(JanusGraphVertex vertex, CompositeIndexT private static IndexRecords indexMatches(JanusGraphVertex vertex, CompositeIndexType index, boolean onlyLoaded, PropertyKey replaceKey, RecordEntry replaceValue) { - IndexRecords matches = new IndexRecords(); - IndexField[] fields = index.getFieldKeys(); + final IndexRecords matches = new IndexRecords(); + final IndexField[] fields = index.getFieldKeys(); indexMatches(vertex,new RecordEntry[fields.length],matches,fields,0,onlyLoaded,replaceKey,replaceValue); return matches; } @@ -469,7 +470,7 @@ private static void indexMatches(JanusGraphVertex vertex, RecordEntry[] current, return; } - PropertyKey key = fields[pos].getFieldKey(); + final PropertyKey key = fields[pos].getFieldKey(); List values; if (key.equals(replaceKey)) { @@ -480,20 +481,20 @@ private static void indexMatches(JanusGraphVertex vertex, RecordEntry[] current, if (onlyLoaded || (!vertex.isNew() && IDManager.VertexIDType.PartitionedVertex.is(vertex.longId()))) { //going through transaction so we can query deleted vertices - VertexCentricQueryBuilder qb = ((InternalVertex)vertex).tx().query(vertex); + final VertexCentricQueryBuilder qb = ((InternalVertex)vertex).tx().query(vertex); qb.noPartitionRestriction().type(key); if (onlyLoaded) qb.queryOnlyLoaded(); props = qb.properties(); } else { props = vertex.query().keys(key.name()).properties(); } - for (JanusGraphVertexProperty p : props) { + for (final JanusGraphVertexProperty p : props) { assert !onlyLoaded || p.isLoaded() || p.isRemoved(); assert key.dataType().equals(p.value().getClass()) : key + " -> " + p; values.add(new RecordEntry(p)); } } - for (RecordEntry value : values) { + for (final RecordEntry value : values) { current[pos]=value; indexMatches(vertex,current,matches,fields,pos+1,onlyLoaded,replaceKey,replaceValue); } @@ -510,8 +511,8 @@ public Stream query(final JointIndexQuery.Subquery query, final BackendT final MultiKeySliceQuery sq = query.getCompositeQuery(); final List rs = sq.execute(tx); final List results = new ArrayList<>(rs.get(0).size()); - for (EntryList r : rs) { - for (java.util.Iterator iterator = r.reuseIterator(); iterator.hasNext(); ) { + for (final EntryList r : rs) { + for (final java.util.Iterator iterator = r.reuseIterator(); iterator.hasNext(); ) { final Entry entry = iterator.next(); final ReadBuffer entryValue = entry.asReadBuffer(); entryValue.movePositionTo(entry.getValuePosition()); @@ -532,27 +533,27 @@ public Stream query(final JointIndexQuery.Subquery query, final BackendT public MultiKeySliceQuery getQuery(final CompositeIndexType index, List values) { final List ksqs = new ArrayList<>(values.size()); - for (Object[] value : values) { + for (final Object[] value : values) { ksqs.add(new KeySliceQuery(getIndexKey(index,value), BufferUtil.zeroBuffer(1), BufferUtil.oneBuffer(1))); } return new MultiKeySliceQuery(ksqs); } public IndexQuery getQuery(final MixedIndexType index, final Condition condition, final OrderList orders) { - Condition newCondition = ConditionUtil.literalTransformation(condition, + final Condition newCondition = ConditionUtil.literalTransformation(condition, new Function, Condition>() { @Nullable @Override public Condition apply(final Condition condition) { Preconditions.checkArgument(condition instanceof PredicateCondition); - PredicateCondition pc = (PredicateCondition) condition; - PropertyKey key = (PropertyKey) pc.getKey(); + final PredicateCondition pc = (PredicateCondition) condition; + final PropertyKey key = (PropertyKey) pc.getKey(); return new PredicateCondition<>(key2Field(index, key), pc.getPredicate(), pc.getValue()); } }); ImmutableList newOrders = IndexQuery.NO_ORDER; if (!orders.isEmpty() && GraphCentricQueryBuilder.indexCoversOrder(index,orders)) { - ImmutableList.Builder lb = ImmutableList.builder(); + final ImmutableList.Builder lb = ImmutableList.builder(); for (int i = 0; i < orders.size(); i++) { lb.add(new IndexQuery.OrderEntry(key2Field(index,orders.getKey(i)), orders.getOrder(i), orders.getKey(i).dataType())); } @@ -568,10 +569,10 @@ public Condition apply(final Condition con private String createQueryString(IndexQueryBuilder query, final ElementCategory resultType, final StandardJanusGraphTx transaction, MixedIndexType index) { Preconditions.checkArgument(index.getElement()==resultType,"Index is not configured for the desired result type: %s",resultType); - String backingIndexName = index.getBackingIndexName(); - IndexProvider indexInformation = (IndexProvider) mixedIndexes.get(backingIndexName); + final String backingIndexName = index.getBackingIndexName(); + final IndexProvider indexInformation = (IndexProvider) mixedIndexes.get(backingIndexName); - StringBuilder qB = new StringBuilder(query.getQuery()); + final StringBuilder qB = new StringBuilder(query.getQuery()); final String prefix = query.getPrefix(); Preconditions.checkNotNull(prefix); //Convert query string by replacing @@ -581,10 +582,10 @@ private String createQueryString(IndexQueryBuilder query, final ElementCategory pos = qB.indexOf(prefix,pos); if (pos<0) break; - int startPos = pos; + final int startPos = pos; pos += prefix.length(); - StringBuilder keyBuilder = new StringBuilder(); - boolean quoteTerminated = qB.charAt(pos)=='"'; + final StringBuilder keyBuilder = new StringBuilder(); + final boolean quoteTerminated = qB.charAt(pos)=='"'; if (quoteTerminated) pos++; while (pos [{}]",replacements,query.getQuery(),queryStr); return queryStr; } - + public Stream executeQuery(IndexQueryBuilder query, final ElementCategory resultType, final BackendTransaction backendTx, final StandardJanusGraphTx transaction) { final MixedIndexType index = getMixedIndex(query.getIndex(), transaction); @@ -649,7 +650,7 @@ public Long executeTotals(IndexQueryBuilder query, final ElementCategory resultT ################################################### */ private static MixedIndexType getMixedIndex(String indexName, StandardJanusGraphTx transaction) { - IndexType index = ManagementSystem.getGraphIndexDirect(indexName, transaction); + final IndexType index = ManagementSystem.getGraphIndexDirect(indexName, transaction); Preconditions.checkArgument(index!=null,"Index with name [%s] is unknown or not configured properly",indexName); Preconditions.checkArgument(index.isMixedIndex()); return (MixedIndexType)index; @@ -698,13 +699,13 @@ private StaticBuffer getIndexKey(CompositeIndexType index, RecordEntry[] record) } private StaticBuffer getIndexKey(CompositeIndexType index, Object[] values) { - DataOutput out = serializer.getDataOutput(8*DEFAULT_OBJECT_BYTELEN + 8); + final DataOutput out = serializer.getDataOutput(8*DEFAULT_OBJECT_BYTELEN + 8); VariableLong.writePositive(out, index.getID()); - IndexField[] fields = index.getFieldKeys(); + final IndexField[] fields = index.getFieldKeys(); Preconditions.checkArgument(fields.length>0 && fields.length==values.length); for (int i = 0; i < fields.length; i++) { - IndexField f = fields[i]; - Object value = values[i]; + final IndexField f = fields[i]; + final Object value = values[i]; Preconditions.checkNotNull(value); if (AttributeUtil.hasGenericDataType(f.getFieldKey())) { out.writeClassAndObject(value); @@ -724,25 +725,25 @@ public long getIndexIdFromKey(StaticBuffer key) { } private Entry getIndexEntry(CompositeIndexType index, RecordEntry[] record, JanusGraphElement element) { - DataOutput out = serializer.getDataOutput(1+8+8*record.length+4*8); + final DataOutput out = serializer.getDataOutput(1+8+8*record.length+4*8); out.putByte(FIRST_INDEX_COLUMN_BYTE); if (index.getCardinality()!=Cardinality.SINGLE) { VariableLong.writePositive(out,element.longId()); if (index.getCardinality()!=Cardinality.SET) { - for (RecordEntry re : record) { + for (final RecordEntry re : record) { VariableLong.writePositive(out,re.relationId); } } } - int valuePosition=out.getPosition(); + final int valuePosition=out.getPosition(); if (element instanceof JanusGraphVertex) { VariableLong.writePositive(out,element.longId()); } else { assert element instanceof JanusGraphRelation; - RelationIdentifier rid = (RelationIdentifier)element.id(); - long[] longs = rid.getLongRepresentation(); + final RelationIdentifier rid = (RelationIdentifier)element.id(); + final long[] longs = rid.getLongRepresentation(); Preconditions.checkArgument(longs.length == 3 || longs.length == 4); - for (long aLong : longs) VariableLong.writePositive(out, aLong); + for (final long aLong : longs) VariableLong.writePositive(out, aLong); } return new StaticArrayEntry(out.getStaticBuffer(),valuePosition); } diff --git a/janusgraph-core/src/main/java/org/janusgraph/graphdb/predicate/AndJanusPredicate.java b/janusgraph-core/src/main/java/org/janusgraph/graphdb/predicate/AndJanusPredicate.java new file mode 100644 index 00000000000..ee4503c09af --- /dev/null +++ b/janusgraph-core/src/main/java/org/janusgraph/graphdb/predicate/AndJanusPredicate.java @@ -0,0 +1,54 @@ +// Copyright 2017 JanusGraph Authors +// +// Licensed 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.janusgraph.graphdb.predicate; + +import java.util.List; +import org.janusgraph.graphdb.query.JanusGraphPredicate; + +/** + * @author David Clement (david.clement90@laposte.net) + */ +public class AndJanusPredicate extends ConnectiveJanusPredicate { + + private static final long serialVersionUID = -4249282297056862327L; + + public AndJanusPredicate(){ + super(); + } + + public AndJanusPredicate(final List predicates) { + super(predicates); + } + + @Override + ConnectiveJanusPredicate getNewNegateIntance() { + return new OrJanusPredicate(); + } + + @Override + boolean isOr() { + return false; + } + + @Override + public boolean isQNF() { + for (final JanusGraphPredicate internalCondition : this) { + if (!internalCondition.isQNF()){ + return false; + } + } + return true; + } +} diff --git a/janusgraph-core/src/main/java/org/janusgraph/graphdb/predicate/ConnectiveJanusGraphP.java b/janusgraph-core/src/main/java/org/janusgraph/graphdb/predicate/ConnectiveJanusGraphP.java new file mode 100644 index 00000000000..c353ff92994 --- /dev/null +++ b/janusgraph-core/src/main/java/org/janusgraph/graphdb/predicate/ConnectiveJanusGraphP.java @@ -0,0 +1,68 @@ +// Copyright 2017 JanusGraph Authors +// +// Licensed 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.janusgraph.graphdb.predicate; + +import java.util.ArrayList; +import java.util.List; + +import org.apache.tinkerpop.gremlin.process.traversal.P; +import org.janusgraph.graphdb.query.JanusGraphPredicate; + +/** + * @author David Clement (david.clement90@laposte.net) + */ +public class ConnectiveJanusGraphP extends P{ + + private static final long serialVersionUID = 1737489543643777182L; + + public ConnectiveJanusGraphP(ConnectiveJanusPredicate biPredicate, List value) { + super(biPredicate, value); + } + @Override + public String toString() { + return toString((ConnectiveJanusPredicate) this.biPredicate, this.originalValue).toString(); + } + + private StringBuilder toString(final JanusGraphPredicate predicate, final Object value) { + final StringBuilder toReturn = new StringBuilder(); + if (!(predicate instanceof ConnectiveJanusPredicate)) { + toReturn.append(predicate); + if (value != null) { + toReturn.append("(").append(value).append(")"); + } + return toReturn; + } + final ConnectiveJanusPredicate connectivePredicate = (ConnectiveJanusPredicate) predicate; + final List values = null == value ? new ArrayList<>() : (List) value; + if (connectivePredicate.size() == 1) { + return toString(connectivePredicate.get(0), values.get(0)); + } + if (predicate instanceof AndJanusPredicate){ + toReturn.append("and("); + } else if (predicate instanceof OrJanusPredicate){ + toReturn.append("or("); + } else { + throw new IllegalArgumentException("JanusGraph does not support the given predicate: " + predicate); + } + if (!connectivePredicate.isEmpty()){ + toReturn.append(toString(connectivePredicate.get(0), values.get(0))); + for (int i = 1; i< connectivePredicate.size(); i++) { + toReturn.append(", ").append(toString(connectivePredicate.get(i), values.get(i))); + } + } + toReturn.append(")"); + return toReturn; + } +} diff --git a/janusgraph-core/src/main/java/org/janusgraph/graphdb/predicate/ConnectiveJanusPredicate.java b/janusgraph-core/src/main/java/org/janusgraph/graphdb/predicate/ConnectiveJanusPredicate.java new file mode 100644 index 00000000000..9d7219e0bef --- /dev/null +++ b/janusgraph-core/src/main/java/org/janusgraph/graphdb/predicate/ConnectiveJanusPredicate.java @@ -0,0 +1,98 @@ +// Copyright 2017 JanusGraph Authors +// +// Licensed 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.janusgraph.graphdb.predicate; + +import java.util.ArrayList; +import java.util.List; +import org.janusgraph.graphdb.query.JanusGraphPredicate; + +/** + * @author David Clement (david.clement90@laposte.net) + */ +public abstract class ConnectiveJanusPredicate extends ArrayList implements JanusGraphPredicate { + + private static final long serialVersionUID = 1558788908114391360L; + + public ConnectiveJanusPredicate(){ + super(); + } + + public ConnectiveJanusPredicate(final List predicates) { + super(predicates); + } + + abstract ConnectiveJanusPredicate getNewNegateIntance(); + + abstract boolean isOr(); + + @Override + @SuppressWarnings("unchecked") + public boolean isValidCondition(Object condition) { + if (!(condition instanceof List) || ((List)condition).size() != this.size()){ + return false; + } + final List conditions = ((List) condition); + for (int i = 0; i< this.size(); i++) { + if(!this.get(i).isValidCondition(conditions.get(i))) { + return false; + } + } + return true; + } + + @Override + public boolean isValidValueType(Class clazz) { + for (final JanusGraphPredicate internalCondition : this) { + if(!(internalCondition.isValidValueType(clazz))){ + return false; + } + } + return true; + } + + @Override + public boolean hasNegation() { + for (final JanusGraphPredicate internalCondition : this) { + if(!(internalCondition.hasNegation())){ + return false; + } + } + return true; + } + + @Override + public JanusGraphPredicate negate() { + final ConnectiveJanusPredicate toReturn = getNewNegateIntance(); + for (final JanusGraphPredicate internalCondition : this) { + toReturn.add(internalCondition.negate()); + } + return toReturn; + } + + @Override + @SuppressWarnings("unchecked") + public boolean test(Object value, Object condition) { + if (!(condition instanceof List) || ((List) condition).size() != this.size()){ + return false; + } + final List conditions = ((List) condition); + for (int i = 0; i< this.size(); i++) { + if(isOr() == this.get(i).test(value, conditions.get(i))) { + return isOr(); + } + } + return !isOr(); + } +} diff --git a/janusgraph-core/src/main/java/org/janusgraph/graphdb/predicate/OrJanusPredicate.java b/janusgraph-core/src/main/java/org/janusgraph/graphdb/predicate/OrJanusPredicate.java new file mode 100644 index 00000000000..75200759b20 --- /dev/null +++ b/janusgraph-core/src/main/java/org/janusgraph/graphdb/predicate/OrJanusPredicate.java @@ -0,0 +1,58 @@ +// Copyright 2017 JanusGraph Authors +// +// Licensed 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.janusgraph.graphdb.predicate; + +import java.util.List; + +import org.janusgraph.graphdb.query.JanusGraphPredicate; + +/** + * @author David Clement (david.clement90@laposte.net) + */ +public class OrJanusPredicate extends ConnectiveJanusPredicate { + + private static final long serialVersionUID = 8069102813023214045L; + + public OrJanusPredicate(){ + super(); + } + + public OrJanusPredicate(final List predicates) { + super(predicates); + } + + @Override + ConnectiveJanusPredicate getNewNegateIntance() { + return new AndJanusPredicate(); + } + + @Override + boolean isOr() { + return true; + } + + @Override + public boolean isQNF() { + for (final JanusGraphPredicate internalCondition : this) { + if (internalCondition instanceof AndJanusPredicate) { + return false; + } + if (!internalCondition.isQNF()){ + return false; + } + } + return true; + } +} diff --git a/janusgraph-core/src/main/java/org/janusgraph/graphdb/query/JanusGraphPredicate.java b/janusgraph-core/src/main/java/org/janusgraph/graphdb/query/JanusGraphPredicate.java index a6e105ba4a2..300e9d237f8 100644 --- a/janusgraph-core/src/main/java/org/janusgraph/graphdb/query/JanusGraphPredicate.java +++ b/janusgraph-core/src/main/java/org/janusgraph/graphdb/query/JanusGraphPredicate.java @@ -16,9 +16,21 @@ import org.janusgraph.core.attribute.Cmp; import org.janusgraph.core.attribute.Contain; +import org.janusgraph.graphdb.predicate.AndJanusPredicate; +import org.janusgraph.graphdb.predicate.ConnectiveJanusGraphP; +import org.janusgraph.graphdb.predicate.ConnectiveJanusPredicate; +import org.janusgraph.graphdb.predicate.OrJanusPredicate; + 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.step.util.HasContainer; +import org.apache.tinkerpop.gremlin.process.traversal.util.AndP; +import org.apache.tinkerpop.gremlin.process.traversal.util.ConnectiveP; +import org.apache.tinkerpop.gremlin.process.traversal.util.OrP; + +import java.util.ArrayList; +import java.util.List; import java.util.function.BiPredicate; /** @@ -64,6 +76,7 @@ public interface JanusGraphPredicate extends BiPredicate { * Returns the negation of this predicate if it exists, otherwise an exception is thrown. Check {@link #hasNegation()} first. * @return */ + @Override JanusGraphPredicate negate(); /** @@ -90,7 +103,7 @@ public static JanusGraphPredicate convertInternal(BiPredicate p) { if (p instanceof JanusGraphPredicate) { return (JanusGraphPredicate)p; } else if (p instanceof Compare) { - Compare comp = (Compare)p; + final Compare comp = (Compare)p; switch(comp) { case eq: return Cmp.EQUAL; case neq: return Cmp.NOT_EQUAL; @@ -101,7 +114,7 @@ public static JanusGraphPredicate convertInternal(BiPredicate p) { default: throw new IllegalArgumentException("Unexpected comparator: " + comp); } } else if (p instanceof Contains) { - Contains con = (Contains)p; + final Contains con = (Contains)p; switch (con) { case within: return Contain.IN; case without: return Contain.NOT_IN; @@ -112,7 +125,7 @@ public static JanusGraphPredicate convertInternal(BiPredicate p) { } public static JanusGraphPredicate convert(BiPredicate p) { - JanusGraphPredicate janusgraphPredicate = convertInternal(p); + final JanusGraphPredicate janusgraphPredicate = convertInternal(p); if (janusgraphPredicate==null) throw new IllegalArgumentException("JanusGraph does not support the given predicate: " + p); return janusgraphPredicate; } @@ -120,6 +133,40 @@ public static JanusGraphPredicate convert(BiPredicate p) { public static boolean supports(BiPredicate p) { return convertInternal(p)!=null; } - } + public static HasContainer convert(final HasContainer container){ + if (!(container.getPredicate() instanceof ConnectiveP)) { + return container; + } + final ConnectiveJanusPredicate connectivePredicate = instanceConnectiveJanusPredicate(container.getPredicate()); + return new HasContainer(container.getKey(), new ConnectiveJanusGraphP(connectivePredicate, convert(((ConnectiveP) container.getPredicate()), connectivePredicate))); + } + + public static ConnectiveJanusPredicate instanceConnectiveJanusPredicate(final P predicate) { + final ConnectiveJanusPredicate connectivePredicate; + if (predicate.getClass().isAssignableFrom(AndP.class)){ + connectivePredicate = new AndJanusPredicate(); + } else if (predicate.getClass().isAssignableFrom(OrP.class)){ + connectivePredicate = new OrJanusPredicate(); + } else { + throw new IllegalArgumentException("JanusGraph does not support the given predicate: " + predicate); + } + return connectivePredicate; + } + + public static List convert(final ConnectiveP predicate, final ConnectiveJanusPredicate connectivePredicate) { + final List toReturn = new ArrayList<>(); + for (final P p : predicate.getPredicates()){ + if (p instanceof ConnectiveP) { + final ConnectiveJanusPredicate subPredicate = instanceConnectiveJanusPredicate(p); + toReturn.add(convert((ConnectiveP)p, subPredicate)); + connectivePredicate.add(subPredicate); + } else { + connectivePredicate.add(Converter.convert(p.getBiPredicate())); + toReturn.add(p.getValue()); + } + } + return toReturn; + } + } } diff --git a/janusgraph-core/src/main/java/org/janusgraph/graphdb/query/QueryUtil.java b/janusgraph-core/src/main/java/org/janusgraph/graphdb/query/QueryUtil.java index 8ff505d7412..257dbdae82a 100644 --- a/janusgraph-core/src/main/java/org/janusgraph/graphdb/query/QueryUtil.java +++ b/janusgraph-core/src/main/java/org/janusgraph/graphdb/query/QueryUtil.java @@ -20,6 +20,9 @@ import org.janusgraph.core.attribute.Cmp; import org.janusgraph.core.attribute.Contain; import org.janusgraph.graphdb.internal.InternalRelationType; +import org.janusgraph.graphdb.predicate.AndJanusPredicate; +import org.janusgraph.graphdb.predicate.ConnectiveJanusPredicate; +import org.janusgraph.graphdb.predicate.OrJanusPredicate; import org.janusgraph.graphdb.query.condition.*; import org.janusgraph.graphdb.transaction.StandardJanusGraphTx; import java.util.*; @@ -36,7 +39,7 @@ public static int adjustLimitForTxModifications(StandardJanusGraphTx tx, int unc assert uncoveredAndConditions >= 0; if (uncoveredAndConditions > 0) { - int maxMultiplier = Integer.MAX_VALUE / limit; + final int maxMultiplier = Integer.MAX_VALUE / limit; limit = limit * Math.min(maxMultiplier, (int) Math.pow(2, uncoveredAndConditions)); //(limit*3)/2+1; } @@ -52,13 +55,18 @@ public static int convertLimit(long limit) { else return (int)limit; } - public static int mergeLimits(int limit1, int limit2) { + public static int mergeLowLimits(int limit1, int limit2) { + assert limit1>=0 && limit2>=0; + return Math.max(limit1,limit2); + } + + public static int mergeHighLimits(int limit1, int limit2) { assert limit1>=0 && limit2>=0; return Math.min(limit1,limit2); } public static InternalRelationType getType(StandardJanusGraphTx tx, String typeName) { - RelationType t = tx.getRelationType(typeName); + final RelationType t = tx.getRelationType(typeName); if (t == null && !tx.getConfiguration().getAutoSchemaMaker().ignoreUndefinedQueryTypes()) { throw new IllegalArgumentException("Undefined type used in query: " + typeName); } @@ -101,7 +109,7 @@ public static boolean isQueryNormalForm(Condition condition) { for (final Condition child : ((And) condition).getChildren()) { if (!isQNFLiteralOrNot(child)) { if (child instanceof Or) { - for (Condition child2 : ((Or) child).getChildren()) { + for (final Condition child2 : ((Or) child).getChildren()) { if (!isQNFLiteralOrNot(child2)) return false; } } else { @@ -127,7 +135,7 @@ private static boolean isQNFLiteral(Condition condition) { public static Condition simplifyQNF(Condition condition) { Preconditions.checkArgument(isQueryNormalForm(condition)); if (condition.numChildren() == 1) { - Condition child = ((And) condition).get(0); + final Condition child = ((And) condition).get(0); if (child.getType() == Condition.Type.LITERAL) return child; } return condition; @@ -149,8 +157,8 @@ public static boolean isEmpty(Condition condition) { */ public static And constraints2QNF(StandardJanusGraphTx tx, List> constraints) { final And conditions = new And<>(constraints.size() + 4); - for (PredicateCondition atom : constraints) { - RelationType type = getType(tx, atom.getKey()); + for (final PredicateCondition atom : constraints) { + final RelationType type = getType(tx, atom.getKey()); if (type == null) { if (atom.getPredicate() == Cmp.EQUAL && atom.getValue() == null || @@ -160,12 +168,12 @@ public static And constraints2QNF(StandardJanus return null; } - Object value = atom.getValue(); - JanusGraphPredicate predicate = atom.getPredicate(); + final Object value = atom.getValue(); + final JanusGraphPredicate predicate = atom.getPredicate(); if (type.isPropertyKey()) { - PropertyKey key = (PropertyKey) type; + final PropertyKey key = (PropertyKey) type; assert predicate.isValidCondition(value); Preconditions.checkArgument(key.dataType()==Object.class || predicate.isValidValueType(key.dataType()), "Data type of key is not compatible with condition"); } else { //its a label @@ -175,10 +183,10 @@ public static And constraints2QNF(StandardJanus if (predicate instanceof Contain) { //Rewrite contains conditions - Collection values = (Collection) value; + final Collection values = (Collection) value; if (predicate == Contain.NOT_IN) { if (values.isEmpty()) continue; //Simply ignore since trivially satisfied - for (Object inValue : values) + for (final Object inValue : values) addConstraint(type, Cmp.NOT_EQUAL, inValue, conditions, tx); } else { Preconditions.checkArgument(predicate == Contain.IN); @@ -188,11 +196,22 @@ public static And constraints2QNF(StandardJanus addConstraint(type, Cmp.EQUAL, values.iterator().next(), conditions, tx); } else { final Or nested = new Or<>(values.size()); - for (Object invalue : values) + for (final Object invalue : values) addConstraint(type, Cmp.EQUAL, invalue, nested, tx); conditions.add(nested); } } + } else if (predicate instanceof AndJanusPredicate) { + if (addConstraint(type, (AndJanusPredicate) (predicate), (List) (value), conditions, tx) == null) { + return null; + } + } else if (predicate instanceof OrJanusPredicate) { + final List values = (List) (value); + final Or nested = addConstraint(type, (OrJanusPredicate) predicate, values, new Or<>(values.size()), tx); + if (nested == null) { + return null; + } + conditions.add(nested); } else { addConstraint(type, predicate, value, conditions, tx); } @@ -200,6 +219,46 @@ public static And constraints2QNF(StandardJanus return conditions; } + private static And addConstraint(final RelationType type, AndJanusPredicate predicate, List values, And and, StandardJanusGraphTx tx) { + for (int i = 0 ; i < values.size(); i++) { + if (predicate.get(i) instanceof AndJanusPredicate) { + if (addConstraint(type, (AndJanusPredicate) (predicate.get(i)), (List) (values.get(i)), and, tx) == null) { + return null; + } + } else if (predicate.get(i) instanceof OrJanusPredicate) { + final List childValues = (List) (values.get(i)); + final Or nested = addConstraint(type, (OrJanusPredicate) (predicate.get(i)), childValues, new Or<>(childValues.size()), tx); + if (nested == null) { + return null; + } + and.add(nested); + } else { + addConstraint(type, predicate.get(i), values.get(i), and, tx); + } + } + return and; + } + + private static Or addConstraint(final RelationType type, OrJanusPredicate predicate, List values, Or or, StandardJanusGraphTx tx) { + for (int i = 0 ; i < values.size(); i++) { + if (predicate.get(i) instanceof AndJanusPredicate) { + final List childValues = (List) (values.get(i)); + final And nested = addConstraint(type, (AndJanusPredicate) (predicate.get(i)), childValues, new And<>(childValues.size()), tx); + if (nested == null) { + return null; + } + or.add(nested); + } else if (predicate.get(i) instanceof OrJanusPredicate) { + if (addConstraint(type, (OrJanusPredicate) (predicate.get(i)), (List) (values.get(i)), or, tx) == null) { + return null; + } + } else { + addConstraint(type, predicate.get(i), values.get(i), or, tx); + } + } + return or; + } + private static void addConstraint(RelationType type, JanusGraphPredicate predicate, Object value, MultiCondition conditions, StandardJanusGraphTx tx) { if (type.isPropertyKey()) { @@ -216,13 +275,13 @@ private static void addConstraint(RelationType typ public static Map.Entry extractOrCondition(Or condition) { RelationType masterType = null; final List values = new ArrayList<>(); - for (Condition c : condition.getChildren()) { + for (final Condition c : condition.getChildren()) { if (!(c instanceof PredicateCondition)) return null; - PredicateCondition atom = (PredicateCondition)c; + final PredicateCondition atom = (PredicateCondition)c; if (atom.getPredicate()!=Cmp.EQUAL) return null; - Object value = atom.getValue(); + final Object value = atom.getValue(); if (value==null) return null; - RelationType type = atom.getKey(); + final RelationType type = atom.getKey(); if (masterType==null) masterType=type; else if (!masterType.equals(type)) return null; values.add(value); @@ -243,18 +302,18 @@ public static List processIntersectingRetrievals(List> retri * of current results with cumulative results on each iteration. */ //TODO: smarter limit estimation - int multiplier = Math.min(16, (int) Math.pow(2, retrievals.size() - 1)); + final int multiplier = Math.min(16, (int) Math.pow(2, retrievals.size() - 1)); int subLimit = Integer.MAX_VALUE; if (Integer.MAX_VALUE / multiplier >= limit) subLimit = limit * multiplier; boolean exhaustedResults; do { exhaustedResults = true; results = null; - for (IndexCall call : retrievals) { + for (final IndexCall call : retrievals) { Collection subResult; try { subResult = call.call(subLimit); - } catch (Exception e) { + } catch (final Exception e) { throw new JanusGraphException("Could not process individual retrieval call ", e); } @@ -262,7 +321,7 @@ public static List processIntersectingRetrievals(List> retri if (results == null) { results = Lists.newArrayList(subResult); } else { - Set subResultSet = ImmutableSet.copyOf(subResult); + final Set subResultSet = ImmutableSet.copyOf(subResult); results.removeIf(o -> !subResultSet.contains(o)); } } diff --git a/janusgraph-core/src/main/java/org/janusgraph/graphdb/query/graph/GraphCentricQuery.java b/janusgraph-core/src/main/java/org/janusgraph/graphdb/query/graph/GraphCentricQuery.java index 209502fb973..93c911a258b 100644 --- a/janusgraph-core/src/main/java/org/janusgraph/graphdb/query/graph/GraphCentricQuery.java +++ b/janusgraph-core/src/main/java/org/janusgraph/graphdb/query/graph/GraphCentricQuery.java @@ -21,7 +21,6 @@ import org.janusgraph.graphdb.query.BackendQueryHolder; import org.janusgraph.graphdb.query.BaseQuery; import org.janusgraph.graphdb.query.ElementQuery; -import org.janusgraph.graphdb.query.QueryUtil; import org.janusgraph.graphdb.query.condition.Condition; import org.janusgraph.graphdb.query.condition.FixedCondition; import org.janusgraph.graphdb.query.profile.ProfileObservable; @@ -64,7 +63,6 @@ public GraphCentricQuery(ElementCategory resultType, Condition> constraints; + private final List> constraints = new ArrayList<>(5); + + private final List>> globalConstraints = new ArrayList<>(); /** * The order in which the elements should be returned. None by default. */ @@ -79,7 +82,6 @@ public GraphCentricQueryBuilder(StandardJanusGraphTx tx, IndexSerializer seriali Preconditions.checkNotNull(serializer); this.tx = tx; this.serializer = serializer; - this.constraints = new ArrayList<>(5); } /* --------------------------------------------------------------- @@ -87,6 +89,10 @@ public GraphCentricQueryBuilder(StandardJanusGraphTx tx, IndexSerializer seriali * --------------------------------------------------------------- */ + public List> getConstraints() { + return constraints; + } + public GraphCentricQueryBuilder profiler(QueryProfiler profiler) { Preconditions.checkNotNull(profiler); this.profiler=profiler; @@ -144,7 +150,7 @@ public GraphCentricQueryBuilder limit(final int limit) { @Override public GraphCentricQueryBuilder orderBy(String keyName, org.apache.tinkerpop.gremlin.process.traversal.Order order) { Preconditions.checkArgument(tx.containsPropertyKey(keyName),"Provided key does not exist: %s",keyName); - PropertyKey key = tx.getPropertyKey(keyName); + final PropertyKey key = tx.getPropertyKey(keyName); Preconditions.checkArgument(key!=null && order!=null,"Need to specify and key and an order"); Preconditions.checkArgument(Comparable.class.isAssignableFrom(key.dataType()), "Can only order on keys with comparable data type. [%s] has datatype [%s]", key.name(), key.dataType()); @@ -155,6 +161,12 @@ public GraphCentricQueryBuilder orderBy(String keyName, org.apache.tinkerpop.gr return this; } + @Override + public GraphCentricQueryBuilder or(GraphCentricQueryBuilder subQuery) { + this.globalConstraints.add(subQuery.getConstraints()); + return this; + } + /* --------------------------------------------------------------- * Query Execution * --------------------------------------------------------------- @@ -162,20 +174,21 @@ public GraphCentricQueryBuilder orderBy(String keyName, org.apache.tinkerpop.gr @Override public Iterable vertices() { - GraphCentricQuery query = constructQuery(ElementCategory.VERTEX); - return Iterables.filter(new QueryProcessor<>(query, tx.elementProcessor), JanusGraphVertex.class); + return iterables(constructQuery(ElementCategory.VERTEX), JanusGraphVertex.class); } @Override public Iterable edges() { - GraphCentricQuery query = constructQuery(ElementCategory.EDGE); - return Iterables.filter(new QueryProcessor<>(query, tx.elementProcessor), JanusGraphEdge.class); + return iterables(constructQuery(ElementCategory.EDGE), JanusGraphEdge.class); } @Override public Iterable properties() { - GraphCentricQuery query = constructQuery(ElementCategory.PROPERTY); - return Iterables.filter(new QueryProcessor<>(query, tx.elementProcessor), JanusGraphVertexProperty.class); + return iterables(constructQuery(ElementCategory.PROPERTY), JanusGraphVertexProperty.class); + } + + public Iterable iterables(final GraphCentricQuery query, final Class aClass) { + return Iterables.filter(new QueryProcessor<>(query, tx.elementProcessor), aClass); } @@ -197,9 +210,12 @@ public Iterable properties() { public GraphCentricQuery constructQuery(final ElementCategory resultType) { - QueryProfiler optProfiler = profiler.addNested(QueryProfiler.OPTIMIZATION); + final QueryProfiler optProfiler = profiler.addNested(QueryProfiler.OPTIMIZATION); optProfiler.startTimer(); - GraphCentricQuery query = constructQueryWithoutProfile(resultType); + if (this.globalConstraints.isEmpty()) { + this.globalConstraints.add(this.constraints); + } + final GraphCentricQuery query = constructQueryWithoutProfile(resultType); optProfiler.stopTimer(); query.observeWith(profiler); return query; @@ -210,8 +226,19 @@ public GraphCentricQuery constructQueryWithoutProfile(final ElementCategory resu if (limit == 0) return GraphCentricQuery.emptyQuery(resultType); //Prepare constraints - And conditions = QueryUtil.constraints2QNF(tx, constraints); - if (conditions == null) return GraphCentricQuery.emptyQuery(resultType); + final MultiCondition conditions; + if (this.globalConstraints.size() == 1) { + conditions = QueryUtil.constraints2QNF(tx, constraints); + if (conditions == null) return GraphCentricQuery.emptyQuery(resultType); + } else { + conditions = new Or<>(); + for (final List> child : this.globalConstraints){ + final And localconditions = QueryUtil.constraints2QNF(tx, child); + if (localconditions == null) return GraphCentricQuery.emptyQuery(resultType); + conditions.add(localconditions); + } + } + //Prepare orders orders.makeImmutable(); @@ -224,7 +251,7 @@ public GraphCentricQuery constructQueryWithoutProfile(final ElementCategory resu final RelationType type = ((PredicateCondition) condition).getKey(); Preconditions.checkArgument(type != null && type.isPropertyKey()); Iterables.addAll(indexCandidates, Iterables.filter(((InternalRelationType) type).getKeyIndexes(), - indexType -> indexType.getElement() == resultType)); + indexType -> indexType.getElement() == resultType && !(conditions instanceof Or && (indexType.isCompositeIndex() || !serializer.features((MixedIndexType) indexType).supportNotQueryNormalForm())))); } return true; }); @@ -235,9 +262,9 @@ Iterate over all potential indexes (as compiled above) and compute a score based this index covers. The index with the highest score (as long as it covers at least one additional clause) is picked and added to the joint query for as long as such exist. */ - JointIndexQuery jointQuery = new JointIndexQuery(); + final JointIndexQuery jointQuery = new JointIndexQuery(); boolean isSorted = orders.isEmpty(); - Set coveredClauses = Sets.newHashSet(); + final Set coveredClauses = Sets.newHashSet(); while (true) { IndexType bestCandidate = null; double candidateScore = 0.0; @@ -245,17 +272,17 @@ this index covers. The index with the highest score (as long as it covers at lea boolean candidateSupportsSort = false; Object candidateSubCondition = null; - for (IndexType index : indexCandidates) { - Set subcover = Sets.newHashSet(); + for (final IndexType index : indexCandidates) { + final Set subcover = Sets.newHashSet(); Object subCondition; boolean supportsSort = orders.isEmpty(); //Check that this index actually applies in case of a schema constraint if (index.hasSchemaTypeConstraint()) { - JanusGraphSchemaType type = index.getSchemaTypeConstraint(); - Map.Entry> equalCon + final JanusGraphSchemaType type = index.getSchemaTypeConstraint(); + final Map.Entry> equalCon = getEqualityConditionValues(conditions,ImplicitKey.LABEL); if (equalCon==null) continue; - Collection labels = equalCon.getValue(); + final Collection labels = equalCon.getValue(); assert labels.size() >= 1; if (labels.size()>1) { log.warn("The query optimizer currently does not support multiple label constraints in query: {}",this); @@ -274,11 +301,10 @@ this index covers. The index with the highest score (as long as it covers at lea if (coveredClauses.isEmpty() && !supportsSort && indexCoversOrder((MixedIndexType)index,orders)) supportsSort=true; } - if (subCondition==null) continue; - assert !subcover.isEmpty(); + if (subCondition==null || subcover.isEmpty()) continue; double score = 0.0; boolean coversAdditionalClause = false; - for (Condition c : subcover) { + for (final Condition c : subcover) { double s = (c instanceof PredicateCondition && ((PredicateCondition)c).getPredicate()==Cmp.EQUAL)? EQUAL_CONDITION_SCORE:OTHER_CONDITION_SCORE; if (coveredClauses.contains(c)) s=s*ALREADY_MATCHED_ADJUSTOR; @@ -343,11 +369,13 @@ public static boolean indexCoversOrder(MixedIndexType index, OrderList orders) { public static List indexCover(final CompositeIndexType index, Condition condition, Set covered) { - assert QueryUtil.isQueryNormalForm(condition); + if (!QueryUtil.isQueryNormalForm(condition)) { + return null; + } assert condition instanceof And; if (index.getStatus()!= SchemaStatus.ENABLED) return null; - IndexField[] fields = index.getFieldKeys(); - Object[] indexValues = new Object[fields.length]; + final IndexField[] fields = index.getFieldKeys(); + final Object[] indexValues = new Object[fields.length]; final Set coveredClauses = new HashSet<>(fields.length); final List indexCovers = new ArrayList<>(4); @@ -364,13 +392,13 @@ private static void constructIndexCover(Object[] indexValues, int position, Inde if (position>=fields.length) { indexCovers.add(indexValues); } else { - IndexField field = fields[position]; - Map.Entry> equalCon = getEqualityConditionValues(condition,field.getFieldKey()); + final IndexField field = fields[position]; + final Map.Entry> equalCon = getEqualityConditionValues(condition,field.getFieldKey()); if (equalCon!=null) { coveredClauses.add(equalCon.getKey()); assert equalCon.getValue().size()>0; - for (Object value : equalCon.getValue()) { - Object[] newValues = Arrays.copyOf(indexValues,fields.length); + for (final Object value : equalCon.getValue()) { + final Object[] newValues = Arrays.copyOf(indexValues,fields.length); newValues[position]=value; constructIndexCover(newValues,position+1,fields,condition,indexCovers,coveredClauses); } @@ -380,14 +408,14 @@ private static void constructIndexCover(Object[] indexValues, int position, Inde private static Map.Entry> getEqualityConditionValues( Condition condition, RelationType type) { - for (Condition c : condition.getChildren()) { + for (final Condition c : condition.getChildren()) { if (c instanceof Or) { - Map.Entry orEqual = QueryUtil.extractOrCondition((Or)c); + final Map.Entry orEqual = QueryUtil.extractOrCondition((Or)c); if (orEqual!=null && orEqual.getKey().equals(type) && !orEqual.getValue().isEmpty()) { return new AbstractMap.SimpleImmutableEntry(c,orEqual.getValue()); } } else if (c instanceof PredicateCondition) { - PredicateCondition atom = (PredicateCondition)c; + final PredicateCondition atom = (PredicateCondition)c; if (atom.getKey().equals(type) && atom.getPredicate()==Cmp.EQUAL && atom.getValue()!=null) { return new AbstractMap.SimpleImmutableEntry(c,ImmutableList.of(atom.getValue())); } @@ -401,10 +429,29 @@ public static Condition indexCover(final MixedIndexType index Condition condition, final IndexSerializer indexInfo, final Set covered) { - assert QueryUtil.isQueryNormalForm(condition); + if (!indexInfo.features(index).supportNotQueryNormalForm() && !QueryUtil.isQueryNormalForm(condition)) { + return null; + } + if (condition instanceof Or) { + for (final Condition subClause : condition.getChildren()) { + if (subClause instanceof And) { + for (final Condition subsubClause : condition.getChildren()) { + if (!coversAll(index, subsubClause,indexInfo)) { + return null; + } + } + } else { + if (!coversAll(index, subClause, indexInfo)) { + return null; + } + } + } + covered.add(condition); + return condition; + } assert condition instanceof And; final And subCondition = new And<>(condition.numChildren()); - for (Condition subClause : condition.getChildren()) { + for (final Condition subClause : condition.getChildren()) { if (coversAll(index,subClause,indexInfo)) { subCondition.add(subClause); covered.add(subClause); @@ -422,7 +469,7 @@ private static boolean coversAll(final MixedIndexType index, Condition atom = (PredicateCondition) condition; + final PredicateCondition atom = (PredicateCondition) condition; if (atom.getValue() == null) { return false; } diff --git a/janusgraph-core/src/main/java/org/janusgraph/graphdb/tinkerpop/optimize/HasStepFolder.java b/janusgraph-core/src/main/java/org/janusgraph/graphdb/tinkerpop/optimize/HasStepFolder.java index 24a53c34870..4710b5dc436 100644 --- a/janusgraph-core/src/main/java/org/janusgraph/graphdb/tinkerpop/optimize/HasStepFolder.java +++ b/janusgraph-core/src/main/java/org/janusgraph/graphdb/tinkerpop/optimize/HasStepFolder.java @@ -19,6 +19,7 @@ import org.apache.tinkerpop.gremlin.process.traversal.step.map.GraphStep; import org.apache.tinkerpop.gremlin.process.traversal.step.map.NoOpBarrierStep; import org.apache.tinkerpop.gremlin.process.traversal.util.AndP; +import org.apache.tinkerpop.gremlin.process.traversal.util.ConnectiveP; import org.janusgraph.core.Cardinality; import org.janusgraph.core.PropertyKey; import org.janusgraph.core.JanusGraphTransaction; @@ -28,11 +29,13 @@ import org.apache.tinkerpop.gremlin.process.traversal.Step; import org.apache.tinkerpop.gremlin.process.traversal.Traversal; import org.apache.tinkerpop.gremlin.process.traversal.step.HasContainerHolder; -import org.apache.tinkerpop.gremlin.process.traversal.step.Ranging; import org.apache.tinkerpop.gremlin.process.traversal.step.filter.HasStep; +import org.apache.tinkerpop.gremlin.process.traversal.step.filter.OrStep; import org.apache.tinkerpop.gremlin.process.traversal.step.filter.RangeGlobalStep; import org.apache.tinkerpop.gremlin.process.traversal.step.map.OrderGlobalStep; import org.apache.tinkerpop.gremlin.process.traversal.step.sideEffect.IdentityStep; +import org.apache.tinkerpop.gremlin.process.traversal.step.util.ElementValueComparator; +import org.apache.tinkerpop.gremlin.process.traversal.step.util.EmptyStep; import org.apache.tinkerpop.gremlin.process.traversal.step.util.HasContainer; import org.javatuples.Pair; @@ -52,15 +55,27 @@ public interface HasStepFolder extends Step { void addAll(Iterable hasContainers); + List addLocalAll(Iterable hasContainers); + void orderBy(String key, Order order); - void setLimit(int limit); + void localOrderBy(List hasContainers, String key, Order order); + + void setLimit(int low, int high); + + void setLocalLimit(List hasContainers, int low, int high); + + int getLowLimit(); + + int getLocalLowLimit(List hasContainers); - int getLimit(); + int getHighLimit(); + + int getLocalHighLimit(List hasContainers); static boolean validJanusGraphHas(HasContainer has) { - if (has.getPredicate() instanceof AndP) { - final List> predicates = ((AndP) has.getPredicate()).getPredicates(); + if (has.getPredicate() instanceof ConnectiveP) { + final List> predicates = ((ConnectiveP) has.getPredicate()).getPredicates(); return predicates.stream().allMatch(p-> validJanusGraphHas(new HasContainer(has.getKey(), p))); } else { return JanusGraphPredicate.Converter.supports(has.getBiPredicate()); @@ -68,7 +83,7 @@ static boolean validJanusGraphHas(HasContainer has) { } static boolean validJanusGraphHas(Iterable has) { - for (HasContainer h : has) { + for (final HasContainer h : has) { if (!validJanusGraphHas(h)) return false; } return true; @@ -77,23 +92,27 @@ static boolean validJanusGraphHas(Iterable has) { static boolean validJanusGraphOrder(OrderGlobalStep orderGlobalStep, Traversal rootTraversal, boolean isVertexOrder) { final List> comparators = orderGlobalStep.getComparators(); - for(Pair comp : comparators) { + for(final Pair comp : comparators) { + final String key; if (comp.getValue0() instanceof ElementValueTraversal && comp.getValue1() instanceof Order) { - final String key = ((ElementValueTraversal) comp.getValue0()).getPropertyKey(); - final JanusGraphTransaction tx = JanusGraphTraversalUtil.getTx(rootTraversal.asAdmin()); - final PropertyKey pKey = tx.getPropertyKey(key); - if(pKey == null - || !(Comparable.class.isAssignableFrom(pKey.dataType())) - || (isVertexOrder && pKey.cardinality() != Cardinality.SINGLE)) { - return false; - } + key = ((ElementValueTraversal) comp.getValue0()).getPropertyKey(); + } else if (comp.getValue1() instanceof ElementValueComparator) { + final ElementValueComparator evc = (ElementValueComparator) comp.getValue1(); + if (!(evc.getValueComparator() instanceof Order)) return false; + key = evc.getPropertyKey(); } else { // do not fold comparators that include nested traversals that are not simple ElementValues return false; } + final JanusGraphTransaction tx = JanusGraphTraversalUtil.getTx(rootTraversal.asAdmin()); + final PropertyKey pKey = tx.getPropertyKey(key); + if (pKey == null + || !(Comparable.class.isAssignableFrom(pKey.dataType())) + || (isVertexOrder && pKey.cardinality() != Cardinality.SINGLE)) { + return false; + } } - return true; } @@ -146,16 +165,43 @@ else if (currentStep instanceof IdentityStep) { } } - static void foldInHasContainer(final HasStepFolder janusgraphStep, final Traversal.Admin traversal) { + static void foldInHasContainer(final HasStepFolder janusgraphStep, final Traversal.Admin traversal, + final Traversal rootTraversal) { Step currentStep = janusgraphStep.getNextStep(); while (true) { - if (currentStep instanceof HasContainerHolder) { - Iterable containers = ((HasContainerHolder) currentStep).getHasContainers(); - if (validJanusGraphHas(containers)) { + if (currentStep instanceof OrStep && janusgraphStep instanceof JanusGraphStep) { + for (final Traversal.Admin child : ((OrStep) currentStep).getLocalChildren()) { + if (!validFoldInHasContainer(child.getStartStep(), false)){ + return; + } + } + ((OrStep) currentStep).getLocalChildren().forEach(t ->localFoldInHasContainer(janusgraphStep, t.getStartStep(), t, rootTraversal)); + traversal.removeStep(currentStep); + } else if (currentStep instanceof HasContainerHolder){ + final Iterable containers = ((HasContainerHolder) currentStep).getHasContainers().stream().map(c -> JanusGraphPredicate.Converter.convert(c)).collect(Collectors.toList()); + if (validFoldInHasContainer(currentStep, true)) { janusgraphStep.addAll(containers); currentStep.getLabels().forEach(janusgraphStep::addLabel); traversal.removeStep(currentStep); } + } else if (!(currentStep instanceof IdentityStep) && !(currentStep instanceof NoOpBarrierStep) && !(currentStep instanceof HasContainerHolder)) { + break; + } + currentStep = currentStep.getNextStep(); + } + } + + static void localFoldInHasContainer(final HasStepFolder janusgraphStep, final Step tinkerpopStep, final Traversal.Admin traversal, + final Traversal rootTraversal){ + Step currentStep = tinkerpopStep; + while (true) { + if (currentStep instanceof HasContainerHolder) { + final Iterable containers = ((HasContainerHolder) currentStep).getHasContainers().stream().map(c -> JanusGraphPredicate.Converter.convert(c)).collect(Collectors.toList()); + final List hasContainers = janusgraphStep.addLocalAll(containers); + currentStep.getLabels().forEach(janusgraphStep::addLabel); + traversal.removeStep(currentStep); + currentStep = foldInOrder(janusgraphStep, currentStep, traversal, rootTraversal, janusgraphStep instanceof JanusGraphStep && ((JanusGraphStep)janusgraphStep).returnsVertex(), hasContainers); + foldInRange(janusgraphStep, currentStep, traversal, hasContainers); } else if (!(currentStep instanceof IdentityStep) && !(currentStep instanceof NoOpBarrierStep)) { break; } @@ -163,9 +209,25 @@ static void foldInHasContainer(final HasStepFolder janusgraphStep, final Travers } } - static void foldInOrder(final HasStepFolder janusgraphStep, final Traversal.Admin traversal, - final Traversal rootTraversal, boolean isVertexOrder) { - Step currentStep = janusgraphStep.getNextStep(); + static boolean validFoldInHasContainer(final Step tinkerpopStep, final boolean defaultValue){ + Step currentStep = tinkerpopStep; + Boolean toReturn = null; + while (!(currentStep instanceof EmptyStep)) { + if (currentStep instanceof HasContainerHolder) { + final Iterable containers = ((HasContainerHolder) currentStep).getHasContainers(); + toReturn = toReturn == null ? validJanusGraphHas(containers) : toReturn && validJanusGraphHas(containers); + } else if (!(currentStep instanceof IdentityStep) && !(currentStep instanceof NoOpBarrierStep) && !(currentStep instanceof RangeGlobalStep) && !(currentStep instanceof OrderGlobalStep)) { + toReturn = toReturn == null ? false : (toReturn && defaultValue); + break; + } + currentStep = currentStep.getNextStep(); + } + return Boolean.TRUE.equals(toReturn); + } + + static Step foldInOrder(final HasStepFolder janusgraphStep, final Step tinkerpopStep, final Traversal.Admin traversal, + final Traversal rootTraversal, boolean isVertexOrder, final List hasContainers) { + Step currentStep = tinkerpopStep; OrderGlobalStep lastOrder = null; while (true) { if (currentStep instanceof OrderGlobalStep) { @@ -180,21 +242,33 @@ static void foldInOrder(final HasStepFolder janusgraphStep, final Traversal.Admi currentStep = currentStep.getNextStep(); } - if (lastOrder != null) { - if (validJanusGraphOrder(lastOrder, rootTraversal, isVertexOrder)) { - //Add orders to HasStepFolder - for (Pair, Comparator> comp : - (List, Comparator>>) ((OrderGlobalStep) lastOrder).getComparators()) { - ElementValueTraversal evt = (ElementValueTraversal) comp.getValue0(); - janusgraphStep.orderBy(evt.getPropertyKey(), (Order) comp.getValue1()); + if (lastOrder != null && validJanusGraphOrder(lastOrder, rootTraversal, isVertexOrder)) { + //Add orders to HasStepFolder + for (final Pair, Comparator> comp : (List, Comparator>>) ((OrderGlobalStep) lastOrder).getComparators()) { + final String key; + final Order order; + if (comp.getValue0() instanceof ElementValueTraversal) { + final ElementValueTraversal evt = (ElementValueTraversal) comp.getValue0(); + key = evt.getPropertyKey(); + order = (Order) comp.getValue1(); + } else { + final ElementValueComparator evc = (ElementValueComparator) comp.getValue1(); + key = evc.getPropertyKey(); + order = (Order) evc.getValueComparator(); + } + if (hasContainers == null) { + janusgraphStep.orderBy(key, order); + } else { + janusgraphStep.localOrderBy(hasContainers, key, order); } - lastOrder.getLabels().forEach(janusgraphStep::addLabel); - traversal.removeStep(lastOrder); } + lastOrder.getLabels().forEach(janusgraphStep::addLabel); + traversal.removeStep(lastOrder); } + return currentStep; } - static void splitAndP(final List hasContainers, final Iterable has) { + static List splitAndP(final List hasContainers, final Iterable has) { has.forEach(hasContainer -> { if (hasContainer.getPredicate() instanceof AndP) { for (final P predicate : ((AndP) hasContainer.getPredicate()).getPredicates()) { @@ -203,6 +277,7 @@ static void splitAndP(final List hasContainers, final Iterable void foldInRange(final HasStepFolder janusgraphStep, final Traversal.Admin traversal) { - Step nextStep = JanusGraphTraversalUtil.getNextNonIdentityStep(janusgraphStep); - + static void foldInRange(final HasStepFolder janusgraphStep, final Step tinkerpopStep, final Traversal.Admin traversal, final List hasContainers) { + final Step nextStep = tinkerpopStep instanceof IdentityStep ? JanusGraphTraversalUtil.getNextNonIdentityStep(tinkerpopStep): tinkerpopStep; if (nextStep instanceof RangeGlobalStep) { - RangeGlobalStep range = (RangeGlobalStep) nextStep; - int limit = QueryUtil.convertLimit(range.getHighRange()); - janusgraphStep.setLimit(QueryUtil.mergeLimits(limit, janusgraphStep.getLimit())); - if (range.getLowRange() == 0) { //Range can be removed since there is no offset + final RangeGlobalStep range = (RangeGlobalStep) nextStep; + int low = 0; + if (janusgraphStep instanceof JanusGraphStep) { + low = QueryUtil.convertLimit(range.getLowRange()); + low = QueryUtil.mergeLowLimits(low, hasContainers == null ? janusgraphStep.getLowLimit(): janusgraphStep.getLocalLowLimit(hasContainers)); + } + int high = QueryUtil.convertLimit(range.getHighRange()); + high = QueryUtil.mergeHighLimits(high, hasContainers == null ? janusgraphStep.getHighLimit(): janusgraphStep.getLocalHighLimit(hasContainers)); + if (hasContainers == null) { + janusgraphStep.setLimit(low, high); + } else { + janusgraphStep.setLocalLimit(hasContainers, low, high); + } + if (janusgraphStep instanceof JanusGraphStep || range.getLowRange() == 0) { //Range can be removed since there is JanusGraphStep or no offset nextStep.getLabels().forEach(janusgraphStep::addLabel); traversal.removeStep(nextStep); } } } - - } diff --git a/janusgraph-core/src/main/java/org/janusgraph/graphdb/tinkerpop/optimize/JanusGraphLocalQueryOptimizerStrategy.java b/janusgraph-core/src/main/java/org/janusgraph/graphdb/tinkerpop/optimize/JanusGraphLocalQueryOptimizerStrategy.java index 0598f2fd399..e1639ed6261 100644 --- a/janusgraph-core/src/main/java/org/janusgraph/graphdb/tinkerpop/optimize/JanusGraphLocalQueryOptimizerStrategy.java +++ b/janusgraph-core/src/main/java/org/janusgraph/graphdb/tinkerpop/optimize/JanusGraphLocalQueryOptimizerStrategy.java @@ -47,10 +47,10 @@ public void apply(final Traversal.Admin traversal) { if (!traversal.getGraph().isPresent()) return; - Graph graph = traversal.getGraph().get(); + final Graph graph = traversal.getGraph().get(); //If this is a compute graph then we can't apply local traversal optimisation at this stage. - StandardJanusGraph janusGraph = graph instanceof StandardJanusGraphTx ? ((StandardJanusGraphTx) graph).getGraph() : (StandardJanusGraph) graph; + final StandardJanusGraph janusGraph = graph instanceof StandardJanusGraphTx ? ((StandardJanusGraphTx) graph).getGraph() : (StandardJanusGraph) graph; final boolean useMultiQuery = !TraversalHelper.onGraphComputer(traversal) && janusGraph.getConfiguration().useMultiQuery(); /* @@ -58,20 +58,20 @@ public void apply(final Traversal.Admin traversal) { */ TraversalHelper.getStepsOfClass(VertexStep.class, traversal).forEach(originalStep -> { - JanusGraphVertexStep vertexStep = new JanusGraphVertexStep(originalStep); + final JanusGraphVertexStep vertexStep = new JanusGraphVertexStep(originalStep); TraversalHelper.replaceStep(originalStep, vertexStep, traversal); if (JanusGraphTraversalUtil.isEdgeReturnStep(vertexStep)) { - HasStepFolder.foldInHasContainer(vertexStep, traversal); + HasStepFolder.foldInHasContainer(vertexStep, traversal, traversal); //We cannot fold in orders or ranges since they are not local } assert JanusGraphTraversalUtil.isEdgeReturnStep(vertexStep) || JanusGraphTraversalUtil.isVertexReturnStep(vertexStep); - Step nextStep = JanusGraphTraversalUtil.getNextNonIdentityStep(vertexStep); + final Step nextStep = JanusGraphTraversalUtil.getNextNonIdentityStep(vertexStep); if (nextStep instanceof RangeGlobalStep) { - int limit = QueryUtil.convertLimit(((RangeGlobalStep) nextStep).getHighRange()); - vertexStep.setLimit(QueryUtil.mergeLimits(limit, vertexStep.getLimit())); + final int limit = QueryUtil.convertLimit(((RangeGlobalStep) nextStep).getHighRange()); + vertexStep.setLimit(0, QueryUtil.mergeHighLimits(limit, vertexStep.getHighLimit())); } if (useMultiQuery) { @@ -86,12 +86,12 @@ public void apply(final Traversal.Admin traversal) { TraversalHelper.getStepsOfClass(PropertiesStep.class, traversal).forEach(originalStep -> { - JanusGraphPropertiesStep propertiesStep = new JanusGraphPropertiesStep(originalStep); + final JanusGraphPropertiesStep propertiesStep = new JanusGraphPropertiesStep(originalStep); TraversalHelper.replaceStep(originalStep, propertiesStep, traversal); if (propertiesStep.getReturnType().forProperties()) { - HasStepFolder.foldInHasContainer(propertiesStep, traversal); + HasStepFolder.foldInHasContainer(propertiesStep, traversal, traversal); //We cannot fold in orders or ranges since they are not local } @@ -105,32 +105,32 @@ public void apply(final Traversal.Admin traversal) { */ TraversalHelper.getStepsOfClass(LocalStep.class, traversal).forEach(localStep -> { - Traversal.Admin localTraversal = ((LocalStep) localStep).getLocalChildren().get(0); - Step localStart = localTraversal.getStartStep(); + final Traversal.Admin localTraversal = ((LocalStep) localStep).getLocalChildren().get(0); + final Step localStart = localTraversal.getStartStep(); if (localStart instanceof VertexStep) { - JanusGraphVertexStep vertexStep = new JanusGraphVertexStep((VertexStep) localStart); + final JanusGraphVertexStep vertexStep = new JanusGraphVertexStep((VertexStep) localStart); TraversalHelper.replaceStep(localStart, vertexStep, localTraversal); if (JanusGraphTraversalUtil.isEdgeReturnStep(vertexStep)) { - HasStepFolder.foldInHasContainer(vertexStep, localTraversal); - HasStepFolder.foldInOrder(vertexStep, localTraversal, traversal, false); + HasStepFolder.foldInHasContainer(vertexStep, localTraversal, traversal); + HasStepFolder.foldInOrder(vertexStep, vertexStep.getNextStep(), localTraversal, traversal, false, null); } - HasStepFolder.foldInRange(vertexStep, localTraversal); + HasStepFolder.foldInRange(vertexStep, JanusGraphTraversalUtil.getNextNonIdentityStep(vertexStep), localTraversal, null); unfoldLocalTraversal(traversal,localStep,localTraversal,vertexStep,useMultiQuery); } if (localStart instanceof PropertiesStep) { - JanusGraphPropertiesStep propertiesStep = new JanusGraphPropertiesStep((PropertiesStep) localStart); + final JanusGraphPropertiesStep propertiesStep = new JanusGraphPropertiesStep((PropertiesStep) localStart); TraversalHelper.replaceStep(localStart, propertiesStep, localTraversal); if (propertiesStep.getReturnType().forProperties()) { - HasStepFolder.foldInHasContainer(propertiesStep, localTraversal); - HasStepFolder.foldInOrder(propertiesStep, localTraversal, traversal, false); + HasStepFolder.foldInHasContainer(propertiesStep, localTraversal, traversal); + HasStepFolder.foldInOrder(propertiesStep, propertiesStep.getNextStep(), localTraversal, traversal, false, null); } - HasStepFolder.foldInRange(propertiesStep, localTraversal); + HasStepFolder.foldInRange(propertiesStep, JanusGraphTraversalUtil.getNextNonIdentityStep(propertiesStep), localTraversal, null); unfoldLocalTraversal(traversal,localStep,localTraversal,propertiesStep,useMultiQuery); diff --git a/janusgraph-core/src/main/java/org/janusgraph/graphdb/tinkerpop/optimize/JanusGraphPropertiesStep.java b/janusgraph-core/src/main/java/org/janusgraph/graphdb/tinkerpop/optimize/JanusGraphPropertiesStep.java index 0a1d1984094..dc6ec090b21 100644 --- a/janusgraph-core/src/main/java/org/janusgraph/graphdb/tinkerpop/optimize/JanusGraphPropertiesStep.java +++ b/janusgraph-core/src/main/java/org/janusgraph/graphdb/tinkerpop/optimize/JanusGraphPropertiesStep.java @@ -14,6 +14,7 @@ package org.janusgraph.graphdb.tinkerpop.optimize; +import com.google.common.base.Preconditions; import com.google.common.collect.Iterables; import com.google.common.collect.Iterators; import org.apache.tinkerpop.gremlin.structure.Property; @@ -62,12 +63,12 @@ public void setUseMultiQuery(boolean useMultiQuery) { } private Q makeQuery(Q query) { - String[] keys = getPropertyKeys(); + final String[] keys = getPropertyKeys(); query.keys(keys); - for (HasContainer condition : hasContainers) { + for (final HasContainer condition : hasContainers) { query.has(condition.getKey(), JanusGraphPredicate.Converter.convert(condition.getBiPredicate()), condition.getValue()); } - for (OrderEntry order : orders) query.orderBy(order.key, order.order); + for (final OrderEntry order : orders) query.orderBy(order.key, order.order); if (limit != BaseQuery.NO_LIMIT) query.limit(limit); ((BasicVertexCentricQueryBuilder) query).profiler(queryProfiler); return query; @@ -88,7 +89,7 @@ private void initialize() { assert getReturnType().forProperties() || (orders.isEmpty() && hasContainers.isEmpty()); if (!starts.hasNext()) throw FastNoSuchElementException.instance(); - List> elements = new ArrayList<>(); + final List> elements = new ArrayList<>(); starts.forEachRemaining(elements::add); starts.add(elements.iterator()); assert elements.size() > 0; @@ -96,7 +97,7 @@ private void initialize() { useMultiQuery = useMultiQuery && elements.stream().allMatch(e -> e.get() instanceof Vertex); if (useMultiQuery) { - JanusGraphMultiVertexQuery multiQuery = JanusGraphTraversalUtil.getTx(traversal).multiQuery(); + final JanusGraphMultiVertexQuery multiQuery = JanusGraphTraversalUtil.getTx(traversal).multiQuery(); elements.forEach(e -> multiQuery.addVertex((Vertex) e.get())); makeQuery(multiQuery); @@ -116,7 +117,7 @@ protected Iterator flatMap(final Traverser.Admin traverser) { assert multiQueryResults != null; return convertIterator(multiQueryResults.get(traverser.get())); } else if (traverser.get() instanceof JanusGraphVertex || traverser.get() instanceof WrappedVertex) { - JanusGraphVertexQuery query = makeQuery((JanusGraphTraversalUtil.getJanusGraphVertex(traverser)).query()); + final JanusGraphVertexQuery query = makeQuery((JanusGraphTraversalUtil.getJanusGraphVertex(traverser)).query()); return convertIterator(query.properties()); } else { //It is some other element (edge or vertex property) @@ -163,21 +164,52 @@ public void addAll(Iterable has) { Iterables.addAll(hasContainers, has); } + @Override + public List addLocalAll(Iterable has) { + throw new UnsupportedOperationException("addLocalAll is not supported for properties step."); + } + @Override public void orderBy(String key, Order order) { orders.add(new HasStepFolder.OrderEntry(key, order)); } @Override - public void setLimit(int limit) { - this.limit = limit; + public void localOrderBy(List hasContainers, String key, Order order) { + throw new UnsupportedOperationException("LocalOrderBy is not supported for properties step."); + } + + @Override + public void setLimit(int low, int high) { + Preconditions.checkArgument(low == 0, "Offset is not supported for properties step."); + this.limit = high; } @Override - public int getLimit() { + public void setLocalLimit(List hasContainers, int low, int high) { + throw new UnsupportedOperationException("LocalLimit is not supported for properties step."); + } + + @Override + public int getLowLimit() { + throw new UnsupportedOperationException("getLowLimit is not supported for properties step."); + } + + @Override + public int getLocalLowLimit(List hasContainers) { + throw new UnsupportedOperationException("getLocalLowLimit is not supported for properties step."); + } + + @Override + public int getHighLimit() { return this.limit; } + @Override + public int getLocalHighLimit(List hasContainers) { + throw new UnsupportedOperationException("getLocalHighLimit is not supported for properties step."); + } + @Override public String toString() { return this.hasContainers.isEmpty() ? super.toString() : StringFactory.stepString(this, this.hasContainers); @@ -187,4 +219,5 @@ public String toString() { public void setMetrics(MutableMetrics metrics) { queryProfiler = new TP3ProfileWrapper(metrics); } + } diff --git a/janusgraph-core/src/main/java/org/janusgraph/graphdb/tinkerpop/optimize/JanusGraphStep.java b/janusgraph-core/src/main/java/org/janusgraph/graphdb/tinkerpop/optimize/JanusGraphStep.java index 3c4f6126041..127fb7f1cb2 100644 --- a/janusgraph-core/src/main/java/org/janusgraph/graphdb/tinkerpop/optimize/JanusGraphStep.java +++ b/janusgraph-core/src/main/java/org/janusgraph/graphdb/tinkerpop/optimize/JanusGraphStep.java @@ -15,13 +15,25 @@ package org.janusgraph.graphdb.tinkerpop.optimize; import org.apache.tinkerpop.gremlin.structure.Graph; +import org.janusgraph.core.JanusGraphEdge; +import org.janusgraph.core.JanusGraphElement; import org.janusgraph.core.JanusGraphQuery; import org.janusgraph.core.JanusGraphTransaction; +import org.janusgraph.core.JanusGraphVertex; +import org.janusgraph.graphdb.internal.ElementCategory; import org.janusgraph.graphdb.query.BaseQuery; import org.janusgraph.graphdb.query.JanusGraphPredicate; +import org.janusgraph.graphdb.query.graph.GraphCentricQuery; import org.janusgraph.graphdb.query.graph.GraphCentricQueryBuilder; import org.janusgraph.graphdb.query.profile.QueryProfiler; import org.janusgraph.graphdb.tinkerpop.profile.TP3ProfileWrapper; +import org.janusgraph.graphdb.util.MultiDistinctOrderedIterator; +import org.javatuples.Triplet; + +import com.google.common.base.Preconditions; +import com.google.common.collect.ArrayListMultimap; +import com.google.common.collect.Multimap; + import org.apache.tinkerpop.gremlin.process.traversal.Order; import org.apache.tinkerpop.gremlin.process.traversal.step.HasContainerHolder; import org.apache.tinkerpop.gremlin.process.traversal.step.Profiling; @@ -36,7 +48,10 @@ import java.util.Arrays; import java.util.Collections; import java.util.Iterator; +import java.util.LinkedHashMap; import java.util.List; +import java.util.Map; +import java.util.Map.Entry; /** * @author Matthias Broecheler (me@matthiasb.com) @@ -44,7 +59,9 @@ public class JanusGraphStep extends GraphStep implements HasStepFolder, Profiling, HasContainerHolder { private final List hasContainers = new ArrayList<>(); - private int limit = BaseQuery.NO_LIMIT; + private final Map, Triplet, Integer, Integer>> hasLocalContainers = new LinkedHashMap<>(); + private int lowLimit = 0; + private int highLimit = BaseQuery.NO_LIMIT; private final List orders = new ArrayList<>(); private QueryProfiler queryProfiler = QueryProfiler.NO_OP; @@ -57,22 +74,116 @@ public JanusGraphStep(final GraphStep originalStep) { final Graph graph = (Graph)traversal.asAdmin().getGraph().get(); return iteratorList((Iterator)graph.vertices(this.ids)); } - JanusGraphTransaction tx = JanusGraphTraversalUtil.getTx(traversal); - JanusGraphQuery query = tx.query(); - for (HasContainer condition : hasContainers) { - query.has(condition.getKey(), JanusGraphPredicate.Converter.convert(condition.getBiPredicate()), condition.getValue()); + if (hasLocalContainers.isEmpty()) { + hasLocalContainers.put(new ArrayList<>(), Triplet.with(new ArrayList<>(), 0, BaseQuery.NO_LIMIT)); + } + final JanusGraphTransaction tx = JanusGraphTraversalUtil.getTx(traversal); + final GraphCentricQuery globalQuery = buildGlobalGraphCentricQuery(tx); + + final Multimap queries = ArrayListMultimap.create(); + if (globalQuery != null && !globalQuery.getSubQuery(0).getBackendQuery().isEmpty()) { + queries.put(0, globalQuery); + } else { + hasLocalContainers.entrySet().forEach(c -> queries.put(c.getValue().getValue1(), buildGraphCentricQuery(tx, c))); } - for (OrderEntry order : orders) query.orderBy(order.key, order.order); - if (limit != BaseQuery.NO_LIMIT) query.limit(limit); - ((GraphCentricQueryBuilder) query).profiler(queryProfiler); - return Vertex.class.isAssignableFrom(this.returnClass) ? query.vertices().iterator() : query.edges().iterator(); + + final GraphCentricQueryBuilder builder = (GraphCentricQueryBuilder) tx.query(); + final List> responses = new ArrayList<>(); + queries.entries().forEach(q -> executeGraphCentryQuery(builder, responses, q)); + + return new MultiDistinctOrderedIterator(lowLimit, highLimit, responses, orders); }); } + private GraphCentricQuery buildGlobalGraphCentricQuery(final JanusGraphTransaction tx) { + //If a query have a local offset or have a local order without a global order and if a query have a limit lower than the global different from other query we can not build globalquery + final Iterator, Integer, Integer>> itQueryInfo = hasLocalContainers.values().iterator(); + Triplet, Integer, Integer> queryInfo = itQueryInfo.next(); + if (queryInfo.getValue1() > 0 || orders.isEmpty() && !queryInfo.getValue0().isEmpty()) { + return null; + } + final Integer limit = queryInfo.getValue2(); + while (itQueryInfo.hasNext()) { + queryInfo = itQueryInfo.next(); + if (queryInfo.getValue1() > 0 || (orders.isEmpty() && !queryInfo.getValue0().isEmpty()) || (queryInfo.getValue2() < highLimit && !limit.equals(queryInfo.getValue2()))) { + return null; + } + } + final JanusGraphQuery query = tx.query(); + for(final List localContainers : hasLocalContainers.keySet()) { + final JanusGraphQuery localQuery = tx.query(); + addConstraint(localQuery, localContainers); + query.or(localQuery); + } + for (final OrderEntry order : orders) query.orderBy(order.key, order.order); + if (highLimit != BaseQuery.NO_LIMIT || limit != BaseQuery.NO_LIMIT) query.limit(Math.min(limit, highLimit)); + Preconditions.checkArgument(query instanceof GraphCentricQueryBuilder); + final GraphCentricQueryBuilder centricQueryBuilder = ((GraphCentricQueryBuilder) query); + centricQueryBuilder.profiler(queryProfiler); + final GraphCentricQuery graphCentricQuery = centricQueryBuilder.constructQuery(Vertex.class.isAssignableFrom(this.returnClass) ? ElementCategory.VERTEX: ElementCategory.EDGE); + return graphCentricQuery; + } + + private void addConstraint(final JanusGraphQuery query, final List localContainers) { + for (final HasContainer condition : hasContainers) { + query.has(condition.getKey(), JanusGraphPredicate.Converter.convert(condition.getBiPredicate()), condition.getValue()); + } + for (final HasContainer condition : localContainers) { + query.has(condition.getKey(), JanusGraphPredicate.Converter.convert(condition.getBiPredicate()), condition.getValue()); + } + } + + private GraphCentricQuery buildGraphCentricQuery(final JanusGraphTransaction tx, + final Entry, Triplet, Integer, Integer>> containers) { + final JanusGraphQuery query = tx.query(); + addConstraint(query, containers.getKey()); + final List realOrders = orders.isEmpty() ? containers.getValue().getValue0() : orders; + for (final OrderEntry order : realOrders) query.orderBy(order.key, order.order); + if (highLimit != BaseQuery.NO_LIMIT || containers.getValue().getValue2() != BaseQuery.NO_LIMIT) query.limit(Math.min(containers.getValue().getValue2(), highLimit)); + Preconditions.checkArgument(query instanceof GraphCentricQueryBuilder); + final GraphCentricQueryBuilder centricQueryBuilder = ((GraphCentricQueryBuilder) query); + centricQueryBuilder.profiler(queryProfiler); + final GraphCentricQuery graphCentricQuery = centricQueryBuilder.constructQuery(Vertex.class.isAssignableFrom(this.returnClass) ? ElementCategory.VERTEX: ElementCategory.EDGE); + return graphCentricQuery; + } + + private void executeGraphCentryQuery(final GraphCentricQueryBuilder builder, final List> responses, + final Entry query) { + final Class classe = Vertex.class.isAssignableFrom(this.returnClass) ? JanusGraphVertex.class: JanusGraphEdge.class; + final Iterator response = (Iterator) builder.iterables(query.getValue(), classe).iterator(); + long i = 0; + while (i < query.getKey() && response.hasNext()) { + response.next(); + i++; + } + responses.add(response); + } + @Override public String toString() { - return this.hasContainers.isEmpty() ? - super.toString() : StringFactory.stepString(this, Arrays.toString(this.ids), this.hasContainers, this.orders); + if (hasLocalContainers.isEmpty() && hasContainers.isEmpty()){ + return super.toString(); + } + if (hasLocalContainers.isEmpty()) { + return StringFactory.stepString(this, Arrays.toString(this.ids), hasContainers); + } + if (hasLocalContainers.size() == 1){ + final List containers = new ArrayList<>(hasContainers); + containers.addAll(hasLocalContainers.keySet().iterator().next()); + return StringFactory.stepString(this, Arrays.toString(this.ids), containers); + } + final StringBuilder sb = new StringBuilder(""); + if (!hasContainers.isEmpty()) { + sb.append(StringFactory.stepString(this, Arrays.toString(ids), hasContainers)).append("."); + } + sb.append("Or("); + final Iterator> itContainers = this.hasLocalContainers.keySet().iterator(); + sb.append(StringFactory.stepString(this, Arrays.toString(this.ids), itContainers.next())); + while(itContainers.hasNext()){ + sb.append(",").append(StringFactory.stepString(this, Arrays.toString(this.ids), itContainers.next())); + } + sb.append(")"); + return sb.toString(); } @Override @@ -80,19 +191,52 @@ public void addAll(Iterable has) { HasStepFolder.splitAndP(hasContainers, has); } + @Override + public List addLocalAll(Iterable has) { + final List containers = HasStepFolder.splitAndP(new ArrayList<>(), has); + hasLocalContainers.put(containers, Triplet.with(new ArrayList<>(), 0, BaseQuery.NO_LIMIT)); + return containers; + } + @Override public void orderBy(String key, Order order) { orders.add(new OrderEntry(key, order)); } @Override - public void setLimit(int limit) { - this.limit = limit; + public void localOrderBy(List containers, String key, Order order) { + hasLocalContainers.get(containers).getValue0().add(new OrderEntry(key, order)); + } + + @Override + public void setLimit(int low, int high) { + this.lowLimit = low; + this.highLimit = high; + } + + @Override + public void setLocalLimit(List containers, int low, int high) { + hasLocalContainers.replace(containers, hasLocalContainers.get(containers).setAt1(low).setAt2(high)); } @Override - public int getLimit() { - return this.limit; + public int getLowLimit() { + return this.lowLimit; + } + + @Override + public int getLocalLowLimit(List containers) { + return hasLocalContainers.get(containers).getValue1(); + } + + @Override + public int getHighLimit() { + return this.highLimit; + } + + @Override + public int getLocalHighLimit(List containers) { + return hasLocalContainers.get(containers).getValue2(); } @Override @@ -102,7 +246,9 @@ public void setMetrics(MutableMetrics metrics) { @Override public List getHasContainers() { - return this.hasContainers; + final List toReturn = new ArrayList<>(this.hasContainers); + this.hasLocalContainers.keySet().stream().forEach(l -> l.stream().forEach(toReturn::add)); + return toReturn; } @Override @@ -110,11 +256,15 @@ public void addHasContainer(final HasContainer hasContainer) { this.addAll(Collections.singleton(hasContainer)); } + public List getOrders() { + return orders; + } + private Iterator iteratorList(final Iterator iterator) { final List list = new ArrayList<>(); while (iterator.hasNext()) { final A e = iterator.next(); - if (HasContainer.testAll(e, this.hasContainers)) + if (HasContainer.testAll(e, this.getHasContainers())) list.add(e); } return list.iterator(); @@ -123,8 +273,10 @@ private Iterator iteratorList(final Iterator iterator) @Override public int hashCode() { int result = super.hashCode(); - result = 31 * result + (hasContainers != null ? hasContainers.hashCode() : 0); - result = 31 * result + limit; + result = 31 * result + (hasContainers != null ? this.hasContainers.hashCode() : 0); + result = 31 * result + (hasLocalContainers != null ? this.hasLocalContainers.hashCode() : 0); + result = 31 * result + lowLimit; + result = 31 * result + highLimit; result = 31 * result + (orders != null ? orders.hashCode() : 0); return result; } diff --git a/janusgraph-core/src/main/java/org/janusgraph/graphdb/tinkerpop/optimize/JanusGraphStepStrategy.java b/janusgraph-core/src/main/java/org/janusgraph/graphdb/tinkerpop/optimize/JanusGraphStepStrategy.java index ba7fac1d909..52a0bc8bad8 100644 --- a/janusgraph-core/src/main/java/org/janusgraph/graphdb/tinkerpop/optimize/JanusGraphStepStrategy.java +++ b/janusgraph-core/src/main/java/org/janusgraph/graphdb/tinkerpop/optimize/JanusGraphStepStrategy.java @@ -44,12 +44,12 @@ public void apply(final Traversal.Admin traversal) { final JanusGraphStep janusGraphStep = new JanusGraphStep<>(originalGraphStep); TraversalHelper.replaceStep(originalGraphStep, janusGraphStep, traversal); HasStepFolder.foldInIds(janusGraphStep, traversal); - HasStepFolder.foldInHasContainer(janusGraphStep, traversal); - HasStepFolder.foldInOrder(janusGraphStep, traversal, traversal, janusGraphStep.returnsVertex()); - HasStepFolder.foldInRange(janusGraphStep, traversal); + HasStepFolder.foldInHasContainer(janusGraphStep, traversal, traversal); + HasStepFolder.foldInOrder(janusGraphStep, janusGraphStep.getNextStep(), traversal, traversal, janusGraphStep.returnsVertex(), null); + HasStepFolder.foldInRange(janusGraphStep, JanusGraphTraversalUtil.getNextNonIdentityStep(janusGraphStep), traversal, null); } else { //Make sure that any provided "start" elements are instantiated in the current transaction - Object[] ids = originalGraphStep.getIds(); + final Object[] ids = originalGraphStep.getIds(); ElementUtils.verifyArgsMustBeEitherIdOrElement(ids); if (ids[0] instanceof Element) { //GraphStep constructor ensures that the entire array is elements @@ -62,6 +62,7 @@ public void apply(final Traversal.Admin traversal) { ((Graph) originalGraphStep.getTraversal().getGraph().get()).edges(elementIds)); } } + }); } diff --git a/janusgraph-core/src/main/java/org/janusgraph/graphdb/tinkerpop/optimize/JanusGraphVertexStep.java b/janusgraph-core/src/main/java/org/janusgraph/graphdb/tinkerpop/optimize/JanusGraphVertexStep.java index d26af09b546..b05f0416eaf 100644 --- a/janusgraph-core/src/main/java/org/janusgraph/graphdb/tinkerpop/optimize/JanusGraphVertexStep.java +++ b/janusgraph-core/src/main/java/org/janusgraph/graphdb/tinkerpop/optimize/JanusGraphVertexStep.java @@ -41,6 +41,8 @@ import java.util.List; import java.util.Map; +import com.google.common.base.Preconditions; + /** * @author Matthias Broecheler (me@matthiasb.com) */ @@ -66,10 +68,10 @@ public void setUseMultiQuery(boolean useMultiQuery) { public Q makeQuery(Q query) { query.labels(getEdgeLabels()); query.direction(getDirection()); - for (HasContainer condition : hasContainers) { + for (final HasContainer condition : hasContainers) { query.has(condition.getKey(), JanusGraphPredicate.Converter.convert(condition.getBiPredicate()), condition.getValue()); } - for (OrderEntry order : orders) query.orderBy(order.key, order.order); + for (final OrderEntry order : orders) query.orderBy(order.key, order.order); if (limit != BaseQuery.NO_LIMIT) query.limit(limit); ((BasicVertexCentricQueryBuilder) query).profiler(queryProfiler); return query; @@ -81,8 +83,8 @@ private void initialize() { initialized = true; if (useMultiQuery) { if (!starts.hasNext()) throw FastNoSuchElementException.instance(); - JanusGraphMultiVertexQuery multiQuery = JanusGraphTraversalUtil.getTx(traversal).multiQuery(); - List> vertices = new ArrayList<>(); + final JanusGraphMultiVertexQuery multiQuery = JanusGraphTraversalUtil.getTx(traversal).multiQuery(); + final List> vertices = new ArrayList<>(); starts.forEachRemaining(v -> { vertices.add(v); multiQuery.addVertex(v.get()); @@ -107,7 +109,7 @@ protected Iterator flatMap(final Traverser.Admin traverser) { assert multiQueryResults != null; return (Iterator) multiQueryResults.get(traverser.get()).iterator(); } else { - JanusGraphVertexQuery query = makeQuery((JanusGraphTraversalUtil.getJanusGraphVertex(traverser)).query()); + final JanusGraphVertexQuery query = makeQuery((JanusGraphTraversalUtil.getJanusGraphVertex(traverser)).query()); return (Vertex.class.isAssignableFrom(getReturnClass())) ? query.vertices().iterator() : query.edges().iterator(); } } @@ -133,27 +135,57 @@ public JanusGraphVertexStep clone() { private int limit; private final List orders = new ArrayList<>(); - @Override public void addAll(Iterable has) { HasStepFolder.splitAndP(hasContainers, has); } + @Override + public List addLocalAll(Iterable has) { + throw new UnsupportedOperationException("addLocalAll is not supported for graph vertex step."); + } + @Override public void orderBy(String key, Order order) { orders.add(new OrderEntry(key, order)); } @Override - public void setLimit(int limit) { - this.limit = limit; + public void localOrderBy(List hasContainers, String key, Order order) { + throw new UnsupportedOperationException("localOrderBy is not supported for graph vertex step."); + } + + @Override + public void setLimit(int low, int high) { + Preconditions.checkArgument(low == 0, "Offset is not supported for properties step."); + this.limit = high; + } + + @Override + public void setLocalLimit(List hasContainers, int low, int high) { + throw new UnsupportedOperationException("setLocalLimit is not supported for graph vertex step."); + } + + @Override + public int getLowLimit() { + throw new UnsupportedOperationException("getLowLimit is not supported for properties step."); + } + + @Override + public int getLocalLowLimit(List hasContainers) { + throw new UnsupportedOperationException("getLocalLowLimit is not supported for properties step."); } @Override - public int getLimit() { + public int getHighLimit() { return this.limit; } + @Override + public int getLocalHighLimit(List hasContainers) { + throw new UnsupportedOperationException("getLocalHighLimit is not supported for graph vertex step."); + } + @Override public String toString() { return this.hasContainers.isEmpty() ? super.toString() : StringFactory.stepString(this, this.hasContainers); diff --git a/janusgraph-core/src/main/java/org/janusgraph/graphdb/util/MultiDistinctOrderedIterator.java b/janusgraph-core/src/main/java/org/janusgraph/graphdb/util/MultiDistinctOrderedIterator.java new file mode 100644 index 00000000000..04b6ebb58fc --- /dev/null +++ b/janusgraph-core/src/main/java/org/janusgraph/graphdb/util/MultiDistinctOrderedIterator.java @@ -0,0 +1,85 @@ +package org.janusgraph.graphdb.util; + +import java.util.ArrayList; +import java.util.Comparator; +import java.util.HashSet; +import java.util.Iterator; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.Spliterator; +import java.util.Spliterators; +import java.util.TreeMap; +import java.util.stream.Stream; +import java.util.stream.StreamSupport; + +import org.apache.tinkerpop.gremlin.process.traversal.step.util.ElementValueComparator; +import org.apache.tinkerpop.gremlin.structure.Element; +import org.apache.tinkerpop.gremlin.util.function.MultiComparator; +import org.janusgraph.graphdb.tinkerpop.optimize.HasStepFolder.OrderEntry; + +import com.google.common.collect.Iterators; + +public class MultiDistinctOrderedIterator implements Iterator { + + private final Map> iterators = new LinkedHashMap<>(); + private final Map values = new LinkedHashMap<>(); + private final TreeMap currentElements; + private final Set allElements = new HashSet<>(); + private final Integer limit; + private long count = 0; + + public MultiDistinctOrderedIterator(final Integer lowLimit, final Integer highLimit, final List> iterators, final List orders) { + this.limit = highLimit; + Comparator comparator = null; + if (orders.isEmpty()) { + final Stream stream = StreamSupport.stream(Spliterators.spliteratorUnknownSize(Iterators.concat(iterators.iterator()), Spliterator.ORDERED), false); + this.iterators.put(0, stream.iterator()); + } else { + final List> comp = new ArrayList<>(); + orders.forEach(o -> comp.add(new ElementValueComparator(o.key, o.order))); + comparator = new MultiComparator<>(comp); + for (int i = 0; i < iterators.size(); i++) { + this.iterators.put(i, iterators.get(i)); + } + } + currentElements = new TreeMap<>(comparator); + long i = 0; + while (i < lowLimit && this.hasNext()) { + this.next(); + i++; + } + } + + @Override + public boolean hasNext() { + if (limit != null && count >= limit) { + return false; + } + for (int i = 0; i < iterators.size(); i++) { + if (!values.containsKey(i) && iterators.get(i).hasNext()){ + E element = null; + do { + element = iterators.get(i).next(); + if (allElements.contains(element)) { + element = null; + } + } while (element == null && iterators.get(i).hasNext()); + if (element != null) { + values.put(i, element); + currentElements.put(element, i); + allElements.add(element); + } + } + } + return !values.isEmpty(); + } + + @Override + public E next() { + count++; + return values.remove(currentElements.remove(currentElements.firstKey())); + } + +} diff --git a/janusgraph-es/src/main/java/org/janusgraph/diskstorage/es/compat/AbstractESCompat.java b/janusgraph-es/src/main/java/org/janusgraph/diskstorage/es/compat/AbstractESCompat.java index a08138389ac..ad5db2f4610 100644 --- a/janusgraph-es/src/main/java/org/janusgraph/diskstorage/es/compat/AbstractESCompat.java +++ b/janusgraph-es/src/main/java/org/janusgraph/diskstorage/es/compat/AbstractESCompat.java @@ -55,6 +55,7 @@ static IndexFeatures.Builder coreFeatures() { .supportsCardinality(Cardinality.SET) .supportsNanoseconds() .supportsCustomAnalyzer() + .supportNotQueryNormalForm() ; } @@ -180,7 +181,7 @@ public Map createRequestBody(ElasticSearchRequest request, Parame if (!request.getSorts().isEmpty()) { requestBody.put("sort", request.getSorts()); } - + Optional.ofNullable(request.getQuery()).ifPresent(parameter -> requestBody.put("query", parameter)); Optional.ofNullable(parameters).ifPresent(p -> Arrays.stream(p).forEachOrdered(parameter -> requestBody.put(parameter.key(), parameter.value()))); return requestBody; diff --git a/janusgraph-lucene/src/main/java/org/janusgraph/diskstorage/lucene/LuceneIndex.java b/janusgraph-lucene/src/main/java/org/janusgraph/diskstorage/lucene/LuceneIndex.java index df7639c2376..03421f0e9dc 100644 --- a/janusgraph-lucene/src/main/java/org/janusgraph/diskstorage/lucene/LuceneIndex.java +++ b/janusgraph-lucene/src/main/java/org/janusgraph/diskstorage/lucene/LuceneIndex.java @@ -88,6 +88,7 @@ public class LuceneIndex implements IndexProvider { .supportsCardinality(Cardinality.SINGLE) .supportsNanoseconds() .supportsGeoContains() + .supportNotQueryNormalForm() .build(); /** @@ -113,8 +114,8 @@ public class LuceneIndex implements IndexProvider { private final String basePath; public LuceneIndex(Configuration config) { - String dir = config.get(GraphDatabaseConfiguration.INDEX_DIRECTORY); - File directory = new File(dir); + final String dir = config.get(GraphDatabaseConfiguration.INDEX_DIRECTORY); + final File directory = new File(dir); if ((!directory.exists() && !directory.mkdirs()) || !directory.isDirectory() || !directory.canWrite()) { throw new IllegalArgumentException("Cannot access or write to directory: " + dir); } @@ -124,15 +125,15 @@ public LuceneIndex(Configuration config) { private Directory getStoreDirectory(String store) throws BackendException { Preconditions.checkArgument(StringUtils.isAlphanumeric(store), "Invalid store name: %s", store); - String dir = basePath + File.separator + store; + final String dir = basePath + File.separator + store; try { - File path = new File(dir); + final File path = new File(dir); if ((!path.exists() && !path.mkdirs()) || !path.isDirectory() || !path.canWrite()) { throw new PermanentBackendException("Cannot access or write to directory: " + dir); } log.debug("Opening store directory [{}]", path); return FSDirectory.open(path.toPath()); - } catch (IOException e) { + } catch (final IOException e) { throw new PermanentBackendException("Could not open directory: " + dir, e); } } @@ -141,12 +142,12 @@ private IndexWriter getWriter(String store) throws BackendException { Preconditions.checkArgument(writerLock.isHeldByCurrentThread()); IndexWriter writer = writers.get(store); if (writer == null) { - IndexWriterConfig iwc = new IndexWriterConfig(analyzer); + final IndexWriterConfig iwc = new IndexWriterConfig(analyzer); iwc.setOpenMode(IndexWriterConfig.OpenMode.CREATE_OR_APPEND); try { writer = new IndexWriter(getStoreDirectory(store), iwc); writers.put(store, writer); - } catch (IOException e) { + } catch (final IOException e) { throw new PermanentBackendException("Could not create writer", e); } } @@ -155,7 +156,7 @@ private IndexWriter getWriter(String store) throws BackendException { private SpatialStrategy getSpatialStrategy(String key, KeyInformation ki) { SpatialStrategy strategy = spatial.get(key); - Mapping mapping = Mapping.getMapping(ki); + final Mapping mapping = Mapping.getMapping(ki); final int maxLevels = ParameterType.INDEX_GEO_MAX_LEVELS.findParameter(ki.getParameters(), DEFAULT_GEO_MAX_LEVELS); final double distErrorPct = ParameterType.INDEX_GEO_DIST_ERROR_PCT.findParameter(ki.getParameters(), @@ -168,7 +169,7 @@ private SpatialStrategy getSpatialStrategy(String key, KeyInformation ki) { if (mapping == Mapping.DEFAULT) { strategy = PointVectorStrategy.newInstance(ctx, key); } else { - SpatialPrefixTree grid = new QuadPrefixTree(ctx, maxLevels); + final SpatialPrefixTree grid = new QuadPrefixTree(ctx, maxLevels); strategy = new RecursivePrefixTreeStrategy(grid, key); ((PrefixTreeStrategy) strategy).setDistErrPct(distErrorPct); } @@ -190,8 +191,8 @@ private static Map spatialPredicates() { @Override public void register(String store, String key, KeyInformation information, BaseTransaction tx) throws BackendException { - Class dataType = information.getDataType(); - Mapping map = Mapping.getMapping(information); + final Class dataType = information.getDataType(); + final Mapping map = Mapping.getMapping(information); Preconditions.checkArgument(map == Mapping.DEFAULT || AttributeUtil.isString(dataType) || (map == Mapping.PREFIX_TREE && AttributeUtil.isGeo(dataType)), "Specified illegal mapping [%s] for data type [%s]", map, dataType); @@ -199,14 +200,14 @@ public void register(String store, String key, KeyInformation information, BaseT @Override public void mutate(Map> mutations, KeyInformation.IndexRetriever information, BaseTransaction tx) throws BackendException { - Transaction ltx = (Transaction) tx; + final Transaction ltx = (Transaction) tx; writerLock.lock(); try { - for (Map.Entry> stores : mutations.entrySet()) { + for (final Map.Entry> stores : mutations.entrySet()) { mutateStores(stores, information); } ltx.postCommit(); - } catch (IOException e) { + } catch (final IOException e) { throw new TemporaryBackendException("Could not update Lucene index", e); } finally { writerLock.unlock(); @@ -216,13 +217,13 @@ public void mutate(Map> mutations, KeyInforma private void mutateStores(Map.Entry> stores, KeyInformation.IndexRetriever information) throws IOException, BackendException { IndexReader reader = null; try { - String storeName = stores.getKey(); - IndexWriter writer = getWriter(storeName); + final String storeName = stores.getKey(); + final IndexWriter writer = getWriter(storeName); reader = DirectoryReader.open(writer, true, true); - IndexSearcher searcher = new IndexSearcher(reader); - for (Map.Entry entry : stores.getValue().entrySet()) { - String documentId = entry.getKey(); - IndexMutation mutation = entry.getValue(); + final IndexSearcher searcher = new IndexSearcher(reader); + for (final Map.Entry entry : stores.getValue().entrySet()) { + final String documentId = entry.getKey(); + final IndexMutation mutation = entry.getValue(); if (mutation.isDeleted()) { if (log.isTraceEnabled()) @@ -232,14 +233,14 @@ private void mutateStores(Map.Entry> stores, continue; } - Pair> docAndGeo = retrieveOrCreate(documentId, searcher); - Document doc = docAndGeo.getKey(); - Map geoFields = docAndGeo.getValue(); + final Pair> docAndGeo = retrieveOrCreate(documentId, searcher); + final Document doc = docAndGeo.getKey(); + final Map geoFields = docAndGeo.getValue(); Preconditions.checkNotNull(doc); - for (IndexEntry del : mutation.getDeletions()) { + for (final IndexEntry del : mutation.getDeletions()) { Preconditions.checkArgument(!del.hasMetaData(), "Lucene index does not support indexing meta data: %s", del); - String key = del.field; + final String key = del.field; if (doc.getField(key) != null) { if (log.isTraceEnabled()) log.trace("Removing field [{}] on document [{}]", key, documentId); @@ -264,15 +265,15 @@ private void mutateStores(Map.Entry> stores, public void restore(Map>> documents, KeyInformation.IndexRetriever information, BaseTransaction tx) throws BackendException { writerLock.lock(); try { - for (Map.Entry>> stores : documents.entrySet()) { - String store = stores.getKey(); - IndexWriter writer = getWriter(store); - IndexReader reader = DirectoryReader.open(writer, true, true); - IndexSearcher searcher = new IndexSearcher(reader); + for (final Map.Entry>> stores : documents.entrySet()) { + final String store = stores.getKey(); + final IndexWriter writer = getWriter(store); + final IndexReader reader = DirectoryReader.open(writer, true, true); + final IndexSearcher searcher = new IndexSearcher(reader); - for (Map.Entry> entry : stores.getValue().entrySet()) { - String docID = entry.getKey(); - List content = entry.getValue(); + for (final Map.Entry> entry : stores.getValue().entrySet()) { + final String docID = entry.getKey(); + final List content = entry.getValue(); if (content == null || content.isEmpty()) { if (log.isTraceEnabled()) @@ -282,7 +283,7 @@ public void restore(Map>> documents, KeyInf continue; } - Pair> docAndGeo = retrieveOrCreate(docID, searcher); + final Pair> docAndGeo = retrieveOrCreate(docID, searcher); addToDocument(store, docID, docAndGeo.getKey(), content, docAndGeo.getValue(), information); //write the old document to the index with the modifications @@ -291,7 +292,7 @@ public void restore(Map>> documents, KeyInf writer.commit(); } tx.commit(); - } catch (IOException e) { + } catch (final IOException e) { throw new TemporaryBackendException("Could not update Lucene index", e); } finally { writerLock.unlock(); @@ -300,8 +301,8 @@ public void restore(Map>> documents, KeyInf private Pair> retrieveOrCreate(String docID, IndexSearcher searcher) throws IOException { Document doc; - TopDocs hits = searcher.search(new TermQuery(new Term(DOCID, docID)), 10); - Map geoFields = Maps.newHashMap(); + final TopDocs hits = searcher.search(new TermQuery(new Term(DOCID, docID)), 10); + final Map geoFields = Maps.newHashMap(); if (hits.scoreDocs.length > 1) throw new IllegalArgumentException("More than one document found for document id: " + docID); @@ -316,14 +317,14 @@ private Pair> retrieveOrCreate(String docID, IndexS if (log.isTraceEnabled()) log.trace("Updating existing document for [{}]", docID); - int docId = hits.scoreDocs[0].doc; + final int docId = hits.scoreDocs[0].doc; //retrieve the old document doc = searcher.doc(docId); - for (IndexableField field : doc.getFields()) { + for (final IndexableField field : doc.getFields()) { if (field.stringValue().startsWith(GEOID)) { try { geoFields.put(field.name(), Geoshape.fromWkt(field.stringValue().substring(GEOID.length())).getShape()); - } catch (java.text.ParseException e) { + } catch (final java.text.ParseException e) { throw new IllegalArgumentException("Geoshape was not parsable"); } } @@ -340,7 +341,7 @@ private void addToDocument(String store, Map geoFields, KeyInformation.IndexRetriever information) { Preconditions.checkNotNull(doc); - for (IndexEntry e : content) { + for (final IndexEntry e : content) { Preconditions.checkArgument(!e.hasMetaData(),"Lucene index does not support indexing meta data: %s",e); if (log.isTraceEnabled()) log.trace("Adding field [{}] on document [{}]", e.field, docID); @@ -361,8 +362,8 @@ private void addToDocument(String store, doc.add(field); doc.add(sortField); } else if (AttributeUtil.isString(e.value)) { - String str = (String) e.value; - Mapping mapping = Mapping.getMapping(store, e.field, information); + final String str = (String) e.value; + final Mapping mapping = Mapping.getMapping(store, e.field, information); Field field; switch(mapping) { case DEFAULT: @@ -376,7 +377,7 @@ private void addToDocument(String store, } doc.add(field); } else if (e.value instanceof Geoshape) { - Shape shape = ((Geoshape) e.value).getShape(); + final Shape shape = ((Geoshape) e.value).getShape(); geoFields.put(e.field, shape); doc.add(new StoredField(e.field, GEOID + e.value.toString())); } else if (e.value instanceof Date) { @@ -387,20 +388,20 @@ private void addToDocument(String store, doc.add(new IntPoint(e.field, ((Boolean)e.value)? 1 : 0)); } else if (e.value instanceof UUID) { //Solr stores UUIDs as strings, we we do the same. - Field field = new StringField(e.field, e.value.toString(), Field.Store.YES); + final Field field = new StringField(e.field, e.value.toString(), Field.Store.YES); doc.add(field); } else { throw new IllegalArgumentException("Unsupported type: " + e.value); } } - for (Map.Entry geo : geoFields.entrySet()) { + for (final Map.Entry geo : geoFields.entrySet()) { if (log.isTraceEnabled()) log.trace("Updating geo-indexes for key {}", geo.getKey()); - KeyInformation ki = information.get(store, geo.getKey()); - SpatialStrategy spatialStrategy = getSpatialStrategy(geo.getKey(), ki); - for (IndexableField f : spatialStrategy.createIndexableFields(geo.getValue())) { + final KeyInformation ki = information.get(store, geo.getKey()); + final SpatialStrategy spatialStrategy = getSpatialStrategy(geo.getKey(), ki); + for (final IndexableField f : spatialStrategy.createIndexableFields(geo.getValue())) { if (doc.getField(f.name()) != null) { doc.removeFields(f.name()); } @@ -413,14 +414,14 @@ private void addToDocument(String store, } private static Sort getSortOrder(IndexQuery query) { - Sort sort = new Sort(); - List orders = query.getOrder(); + final Sort sort = new Sort(); + final List orders = query.getOrder(); if (!orders.isEmpty()) { - SortField[] fields = new SortField[orders.size()]; + final SortField[] fields = new SortField[orders.size()]; for (int i = 0; i < orders.size(); i++) { - IndexQuery.OrderEntry order = orders.get(i); + final IndexQuery.OrderEntry order = orders.get(i); SortField.Type sortType = null; - Class dataType = order.getDatatype(); + final Class dataType = order.getDatatype(); if (AttributeUtil.isString(dataType)) sortType = SortField.Type.STRING; else if (AttributeUtil.isWholeNumber(dataType)) sortType = SortField.Type.LONG; else if (AttributeUtil.isDecimal(dataType)) sortType = SortField.Type.DOUBLE; @@ -437,17 +438,17 @@ private static Sort getSortOrder(IndexQuery query) { @Override public Stream query(IndexQuery query, KeyInformation.IndexRetriever information, BaseTransaction tx) throws BackendException { //Construct query - SearchParams searchParams = convertQuery(query.getCondition(), information.get(query.getStore())); + final SearchParams searchParams = convertQuery(query.getCondition(), information.get(query.getStore())); try { - IndexSearcher searcher = ((Transaction) tx).getSearcher(query.getStore()); + final IndexSearcher searcher = ((Transaction) tx).getSearcher(query.getStore()); if (searcher == null) return Collections.unmodifiableList(new ArrayList()).stream(); //Index does not yet exist Query q = searchParams.getQuery(); if (null == q) q = new MatchAllDocsQuery(); - long time = System.currentTimeMillis(); + final long time = System.currentTimeMillis(); final TopDocs docs = searcher.search(q, query.hasLimit() ? query.getLimit() : Integer.MAX_VALUE - 1, getSortOrder(query)); log.debug("Executed query [{}] in {} ms", q, System.currentTimeMillis() - time); final List result = new ArrayList<>(docs.scoreDocs.length); @@ -456,7 +457,7 @@ public Stream query(IndexQuery query, KeyInformation.IndexRetriever info result.add(field == null ? null : field.stringValue()); } return result.stream(); - } catch (IOException e) { + } catch (final IOException e) { throw new TemporaryBackendException("Could not execute Lucene query", e); } } @@ -468,7 +469,7 @@ private static Query numericQuery(String key, Cmp relation, Number value) { LongPoint.newRangeQuery(key, value.longValue(), value.longValue()) : DoublePoint.newRangeQuery(key, value.doubleValue(), value.doubleValue()); case NOT_EQUAL: - BooleanQuery.Builder q = new BooleanQuery.Builder(); + final BooleanQuery.Builder q = new BooleanQuery.Builder(); if (AttributeUtil.isWholeNumber(value)) { q.add(LongPoint.newRangeQuery(key, Long.MIN_VALUE, Math.addExact(value.longValue(), -1)), BooleanClause.Occur.SHOULD); q.add(LongPoint.newRangeQuery(key, Math.addExact(value.longValue(), 1), Long.MAX_VALUE), BooleanClause.Occur.SHOULD); @@ -499,17 +500,17 @@ private static Query numericQuery(String key, Cmp relation, Number value) { } private SearchParams convertQuery(Condition condition, KeyInformation.StoreRetriever information) { - SearchParams params = new SearchParams(); + final SearchParams params = new SearchParams(); if (condition instanceof PredicateCondition) { - PredicateCondition atom = (PredicateCondition) condition; + final PredicateCondition atom = (PredicateCondition) condition; Object value = atom.getValue(); - String key = atom.getKey(); - JanusGraphPredicate janusgraphPredicate = atom.getPredicate(); + final String key = atom.getKey(); + final JanusGraphPredicate janusgraphPredicate = atom.getPredicate(); if (value instanceof Number) { Preconditions.checkArgument(janusgraphPredicate instanceof Cmp, "Relation not supported on numeric types: " + janusgraphPredicate); params.addQuery(numericQuery(key, (Cmp) janusgraphPredicate, (Number) value)); } else if (value instanceof String) { - Mapping map = Mapping.getMapping(information.get(key)); + final Mapping map = Mapping.getMapping(information.get(key)); if ((map==Mapping.DEFAULT || map==Mapping.TEXT) && !Text.HAS_CONTAINS.contains(janusgraphPredicate)) throw new IllegalArgumentException("Text mapped string values only support CONTAINS queries and not: " + janusgraphPredicate); if (map==Mapping.STRING && Text.HAS_CONTAINS.contains(janusgraphPredicate)) @@ -519,8 +520,8 @@ private SearchParams convertQuery(Condition condition, KeyInformation.StoreRe if (janusgraphPredicate == Text.CONTAINS) { value = ((String) value).toLowerCase(); - BooleanQuery.Builder b = new BooleanQuery.Builder(); - for (String term : Text.tokenize((String)value)) { + final BooleanQuery.Builder b = new BooleanQuery.Builder(); + for (final String term : Text.tokenize((String)value)) { b.add(new TermQuery(new Term(key, term)), BooleanClause.Occur.MUST); } params.addQuery(b.build()); @@ -530,16 +531,16 @@ private SearchParams convertQuery(Condition condition, KeyInformation.StoreRe } else if (janusgraphPredicate == Text.PREFIX) { params.addQuery(new PrefixQuery(new Term(key, (String) value))); } else if (janusgraphPredicate == Text.REGEX) { - RegexpQuery rq = new RegexpQuery(new Term(key, (String) value)); + final RegexpQuery rq = new RegexpQuery(new Term(key, (String) value)); params.addQuery(rq); } else if (janusgraphPredicate == Text.CONTAINS_REGEX) { // This is terrible -- there is probably a better way - RegexpQuery rq = new RegexpQuery(new Term(key, ".*" + (value) + ".*")); + final RegexpQuery rq = new RegexpQuery(new Term(key, ".*" + (value) + ".*")); params.addQuery(rq); } else if (janusgraphPredicate == Cmp.EQUAL) { params.addQuery(new TermQuery(new Term(key,(String)value))); } else if (janusgraphPredicate == Cmp.NOT_EQUAL) { - BooleanQuery.Builder q = new BooleanQuery.Builder(); + final BooleanQuery.Builder q = new BooleanQuery.Builder(); q.add(new MatchAllDocsQuery(), BooleanClause.Occur.MUST); q.add(new TermQuery(new Term(key, (String) value)), BooleanClause.Occur.MUST_NOT); params.addQuery(q.build()); @@ -547,8 +548,8 @@ private SearchParams convertQuery(Condition condition, KeyInformation.StoreRe params.addQuery(new FuzzyQuery(new Term(key, (String) value))); } else if(janusgraphPredicate == Text.CONTAINS_FUZZY){ value = ((String) value).toLowerCase(); - Builder b = new BooleanQuery.Builder(); - for (String term : Text.tokenize((String)value)) { + final Builder b = new BooleanQuery.Builder(); + for (final String term : Text.tokenize((String)value)) { b.add(new FuzzyQuery(new Term(key, term)),BooleanClause.Occur.MUST); } params.addQuery(b.build()); @@ -587,7 +588,7 @@ private SearchParams convertQuery(Condition condition, KeyInformation.StoreRe if (janusgraphPredicate == Cmp.EQUAL) { params.addQuery(new TermQuery(new Term(key, value.toString()))); } else if (janusgraphPredicate == Cmp.NOT_EQUAL) { - BooleanQuery.Builder q = new BooleanQuery.Builder(); + final BooleanQuery.Builder q = new BooleanQuery.Builder(); q.add(new MatchAllDocsQuery(), BooleanClause.Occur.MUST); q.add(new TermQuery(new Term(key, value.toString())), BooleanClause.Occur.MUST_NOT); params.addQuery(q.build()); @@ -599,17 +600,17 @@ private SearchParams convertQuery(Condition condition, KeyInformation.StoreRe throw new IllegalArgumentException("Unsupported type: " + value); } } else if (condition instanceof Not) { - SearchParams childParams = convertQuery(((Not) condition).getChild(), information); + final SearchParams childParams = convertQuery(((Not) condition).getChild(), information); params.addQuery(new MatchAllDocsQuery(), BooleanClause.Occur.MUST); params.addParams(childParams, BooleanClause.Occur.MUST_NOT); } else if (condition instanceof And) { - for (Condition c : condition.getChildren()) { - SearchParams childParams = convertQuery(c, information); + for (final Condition c : condition.getChildren()) { + final SearchParams childParams = convertQuery(c, information); params.addParams(childParams, BooleanClause.Occur.MUST); } } else if (condition instanceof Or) { - for (Condition c : condition.getChildren()) { - SearchParams childParams = convertQuery(c, information); + for (final Condition c : condition.getChildren()) { + final SearchParams childParams = convertQuery(c, information); params.addParams(childParams, BooleanClause.Occur.SHOULD); } } else throw new IllegalArgumentException("Invalid condition: " + condition); @@ -622,16 +623,16 @@ public Stream> query(RawQuery query, KeyInformation.Inde Query q; try { q = new QueryParser("_all",analyzer).parse(query.getQuery()); - // Lucene query parser does not take additional parameters so any parameters on the RawQuery are ignored. - } catch (ParseException e) { + // Lucene query parser does not take additional parameters so any parameters on the RawQuery are ignored. + } catch (final ParseException e) { throw new PermanentBackendException("Could not parse raw query: "+query.getQuery(),e); } try { - IndexSearcher searcher = ((Transaction) tx).getSearcher(query.getStore()); + final IndexSearcher searcher = ((Transaction) tx).getSearcher(query.getStore()); if (searcher == null) return Collections.unmodifiableList(new ArrayList>()).stream(); //Index does not yet exist - long time = System.currentTimeMillis(); + final long time = System.currentTimeMillis(); //TODO: can we make offset more efficient in Lucene? final int offset = query.getOffset(); int adjustedLimit = query.hasLimit() ? query.getLimit() : Integer.MAX_VALUE - 1; @@ -645,7 +646,7 @@ public Stream> query(RawQuery query, KeyInformation.Inde result.add(new RawQuery.Result<>(field == null ? null : field.stringValue(), docs.scoreDocs[i].score)); } return result.stream(); - } catch (IOException e) { + } catch (final IOException e) { throw new TemporaryBackendException("Could not execute Lucene query", e); } } @@ -655,7 +656,7 @@ public Long totals(RawQuery query, KeyInformation.IndexRetriever information, Ba final Query q; try { q = new QueryParser("_all",analyzer).parse(query.getQuery()); - } catch (ParseException e) { + } catch (final ParseException e) { throw new PermanentBackendException("Could not parse raw query: "+query.getQuery(),e); } @@ -670,11 +671,11 @@ public Long totals(RawQuery query, KeyInformation.IndexRetriever information, Ba final TopDocs docs = searcher.search(q, 1); log.debug("Executed query [{}] in {} ms",q, System.currentTimeMillis() - time); return docs.totalHits; - } catch (IOException e) { + } catch (final IOException e) { throw new TemporaryBackendException("Could not execute Lucene query", e); } } - + @Override public BaseTransactionConfigurable beginTransaction(BaseTransactionConfig config) throws BackendException { return new Transaction(config); @@ -683,8 +684,8 @@ public BaseTransactionConfigurable beginTransaction(BaseTransactionConfig config @Override public boolean supports(KeyInformation information, JanusGraphPredicate janusgraphPredicate) { if (information.getCardinality()!= Cardinality.SINGLE) return false; - Class dataType = information.getDataType(); - Mapping mapping = Mapping.getMapping(information); + final Class dataType = information.getDataType(); + final Mapping mapping = Mapping.getMapping(information); if (mapping!=Mapping.DEFAULT && !AttributeUtil.isString(dataType) && !(mapping==Mapping.PREFIX_TREE && AttributeUtil.isGeo(dataType))) return false; @@ -713,8 +714,8 @@ public boolean supports(KeyInformation information, JanusGraphPredicate janusgra @Override public boolean supports(KeyInformation information) { if (information.getCardinality()!= Cardinality.SINGLE) return false; - Class dataType = information.getDataType(); - Mapping mapping = Mapping.getMapping(information); + final Class dataType = information.getDataType(); + final Mapping mapping = Mapping.getMapping(information); if (Number.class.isAssignableFrom(dataType) || dataType == Date.class || dataType == Instant.class || dataType == Boolean.class || dataType == UUID.class) { return mapping == Mapping.DEFAULT; } else if (AttributeUtil.isString(dataType)) { @@ -739,8 +740,8 @@ public IndexFeatures getFeatures() { @Override public void close() throws BackendException { try { - for (IndexWriter w : writers.values()) w.close(); - } catch (IOException e) { + for (final IndexWriter w : writers.values()) w.close(); + } catch (final IOException e) { throw new PermanentBackendException("Could not close writers", e); } } @@ -749,7 +750,7 @@ public void close() throws BackendException { public void clearStorage() throws BackendException { try { FileUtils.deleteDirectory(new File(basePath)); - } catch (IOException e) { + } catch (final IOException e) { throw new PermanentBackendException("Could not delete lucene directory: " + basePath, e); } } @@ -759,7 +760,7 @@ public boolean exists() throws BackendException { if (Files.exists(Paths.get(basePath))) { try (final DirectoryStream dirStream = Files.newDirectoryStream(Paths.get(basePath))) { return dirStream.iterator().hasNext(); - } catch (IOException e) { + } catch (final IOException e) { throw new PermanentBackendException("Could not read lucene directory: " + basePath, e); } } else { @@ -784,9 +785,9 @@ private synchronized IndexSearcher getSearcher(String store) throws BackendExcep try { reader = DirectoryReader.open(getStoreDirectory(store)); searcher = new IndexSearcher(reader); - } catch (IndexNotFoundException e) { + } catch (final IndexNotFoundException e) { searcher = null; - } catch (IOException e) { + } catch (final IOException e) { throw new PermanentBackendException("Could not open index reader on store: " + store, e); } searchers.put(store, searcher); @@ -812,10 +813,10 @@ public void rollback() throws BackendException { private void close() throws BackendException { try { - for (IndexSearcher searcher : searchers.values()) { + for (final IndexSearcher searcher : searchers.values()) { if (searcher != null) searcher.getIndexReader().close(); } - } catch (IOException e) { + } catch (final IOException e) { throw new PermanentBackendException("Could not close searcher", e); } } @@ -842,7 +843,7 @@ private void addQuery(Query newQuery, BooleanClause.Occur occur) { } private void addParams(SearchParams other, BooleanClause.Occur occur) { - Query otherQuery = other.getQuery(); + final Query otherQuery = other.getQuery(); if (null != otherQuery) addQuery(otherQuery, occur); } diff --git a/janusgraph-solr/src/test/resources/collections.txt b/janusgraph-solr/src/test/resources/collections.txt index 26dd6017490..8b8a89f19c0 100644 --- a/janusgraph-solr/src/test/resources/collections.txt +++ b/janusgraph-solr/src/test/resources/collections.txt @@ -1 +1 @@ -store1 store2 vertex edge namev namee composite psearch esearch vsearch mi mixed index1 index2 index3 ecategory vcategory pcategory theIndex vertices edges booleanIndex dateIndex instantIndex uuidIndex randomMixedIndex collectionIndex +store1 store2 vertex edge namev namee composite psearch esearch vsearch mi mixed index1 index2 index3 ecategory vcategory pcategory theIndex vertices edges booleanIndex dateIndex instantIndex uuidIndex randomMixedIndex collectionIndex nameidx oridx otheridx lengthidx diff --git a/janusgraph-test/src/main/java/org/janusgraph/graphdb/JanusGraphBaseTest.java b/janusgraph-test/src/main/java/org/janusgraph/graphdb/JanusGraphBaseTest.java index 2df37a747f3..00eb1aac662 100644 --- a/janusgraph-test/src/main/java/org/janusgraph/graphdb/JanusGraphBaseTest.java +++ b/janusgraph-test/src/main/java/org/janusgraph/graphdb/JanusGraphBaseTest.java @@ -82,10 +82,10 @@ public static void clearGraph(WriteConfiguration config) throws BackendException } public static Backend getBackend(WriteConfiguration config, boolean initialize) throws BackendException { - ModifiableConfiguration adjustedConfig = new ModifiableConfiguration(GraphDatabaseConfiguration.ROOT_NS,config.copy(), BasicConfiguration.Restriction.NONE); + final ModifiableConfiguration adjustedConfig = new ModifiableConfiguration(GraphDatabaseConfiguration.ROOT_NS,config.copy(), BasicConfiguration.Restriction.NONE); adjustedConfig.set(GraphDatabaseConfiguration.LOCK_LOCAL_MEDIATOR_GROUP, "tmp"); adjustedConfig.set(GraphDatabaseConfiguration.UNIQUE_INSTANCE_ID, "inst"); - Backend backend = new Backend(adjustedConfig); + final Backend backend = new Backend(adjustedConfig); if (initialize) { backend.initialize(adjustedConfig); } @@ -144,7 +144,7 @@ public void newTx() { public static Map validateConfigOptions(Object... settings) { //Parse settings Preconditions.checkArgument(settings.length%2==0,"Expected even number of settings: %s",settings); - Map options = Maps.newHashMap(); + final Map options = Maps.newHashMap(); for (int i=0;i0) { - Map options = validateConfigOptions(settings); + final Map options = validateConfigOptions(settings); JanusGraphManagement janusGraphManagement = null; - ModifiableConfiguration modifiableConfiguration = new ModifiableConfiguration(GraphDatabaseConfiguration.ROOT_NS,config, BasicConfiguration.Restriction.LOCAL); - for (Map.Entry option : options.entrySet()) { + final ModifiableConfiguration modifiableConfiguration = new ModifiableConfiguration(GraphDatabaseConfiguration.ROOT_NS,config, BasicConfiguration.Restriction.LOCAL); + for (final Map.Entry option : options.entrySet()) { if (option.getKey().option.isLocal()) { modifiableConfiguration.set(option.getKey().option,option.getValue(),option.getKey().umbrella); } else { @@ -206,13 +206,13 @@ public TestConfigOption(ConfigOption option, String... umbrella) { private void closeLogs() { try { - for (LogManager lm : logManagers.values()) lm.close(); + for (final LogManager lm : logManagers.values()) lm.close(); logManagers.clear(); if (logStoreManager!=null) { logStoreManager.close(); logStoreManager=null; } - } catch (BackendException e) { + } catch (final BackendException e) { throw new JanusGraphException(e); } } @@ -221,7 +221,7 @@ public void closeLogManager(String logManagerName) { if (logManagers.containsKey(logManagerName)) { try { logManagers.remove(logManagerName).close(); - } catch (BackendException e) { + } catch (final BackendException e) { throw new JanusGraphException("Could not close log manager " + logManagerName,e); } } @@ -237,28 +237,28 @@ public Log openTxLog() { private Log openLog(String logManagerName, String logName) { try { - ModifiableConfiguration configuration = new ModifiableConfiguration(GraphDatabaseConfiguration.ROOT_NS,config.copy(), BasicConfiguration.Restriction.NONE); + final ModifiableConfiguration configuration = new ModifiableConfiguration(GraphDatabaseConfiguration.ROOT_NS,config.copy(), BasicConfiguration.Restriction.NONE); configuration.set(GraphDatabaseConfiguration.UNIQUE_INSTANCE_ID, "reader"); configuration.set(GraphDatabaseConfiguration.LOG_READ_INTERVAL, Duration.ofMillis(500L), logManagerName); if (logStoreManager==null) { logStoreManager = Backend.getStorageManager(configuration); } - StoreFeatures f = logStoreManager.getFeatures(); - boolean part = f.isDistributed() && f.isKeyOrdered(); + final StoreFeatures f = logStoreManager.getFeatures(); + final boolean part = f.isDistributed() && f.isKeyOrdered(); if (part) { - for (String partitionedLogName : new String[]{USER_LOG,TRANSACTION_LOG,MANAGEMENT_LOG}) + for (final String partitionedLogName : new String[]{USER_LOG,TRANSACTION_LOG,MANAGEMENT_LOG}) configuration.set(KCVSLogManager.LOG_MAX_PARTITIONS,8,partitionedLogName); } assert logStoreManager!=null; if (!logManagers.containsKey(logManagerName)) { //Open log manager - only supports KCVSLog - Configuration logConfig = configuration.restrictTo(logManagerName); + final Configuration logConfig = configuration.restrictTo(logManagerName); Preconditions.checkArgument(logConfig.get(LOG_BACKEND).equals(LOG_BACKEND.getDefaultValue())); logManagers.put(logManagerName,new KCVSLogManager(logStoreManager,logConfig)); } assert logManagers.containsKey(logManagerName); return logManagers.get(logManagerName).openLog(logName); - } catch (BackendException e) { + } catch (final BackendException e) { throw new JanusGraphException("Could not open log: "+ logName,e); } } @@ -268,13 +268,13 @@ private Log openLog(String logManagerName, String logName) { */ public PropertyKey makeVertexIndexedKey(String name, Class dataType) { - PropertyKey key = mgmt.makePropertyKey(name).dataType(dataType).cardinality(Cardinality.SINGLE).make(); + final PropertyKey key = mgmt.makePropertyKey(name).dataType(dataType).cardinality(Cardinality.SINGLE).make(); mgmt.buildIndex(name,Vertex.class).addKey(key).buildCompositeIndex(); return key; } public PropertyKey makeVertexIndexedUniqueKey(String name, Class dataType) { - PropertyKey key = mgmt.makePropertyKey(name).dataType(dataType).cardinality(Cardinality.SINGLE).make(); + final PropertyKey key = mgmt.makePropertyKey(name).dataType(dataType).cardinality(Cardinality.SINGLE).make(); mgmt.buildIndex(name,Vertex.class).addKey(key).unique().buildCompositeIndex(); return key; } @@ -294,7 +294,7 @@ public JanusGraphIndex getExternalIndex(Class clazz, String b else if (JanusGraphVertexProperty.class.isAssignableFrom(clazz)) prefix = "p"; else throw new AssertionError(clazz.toString()); - String indexName = prefix+backingIndex; + final String indexName = prefix+backingIndex; JanusGraphIndex index = mgmt.getGraphIndex(indexName); if (index==null) { index = mgmt.buildIndex(indexName,clazz).buildMixedIndex(backingIndex); @@ -325,7 +325,7 @@ public EdgeLabel makeKeyedEdgeLabel(String name, PropertyKey sort, PropertyKey s public static final int DEFAULT_THREAD_COUNT = 4; public static int getThreadCount() { - String s = System.getProperty("janusgraph.test.threads"); + final String s = System.getProperty("janusgraph.test.threads"); if (null != s) return Integer.valueOf(s); else @@ -381,7 +381,7 @@ public static E getOnlyElement(Iterable traversal, E defaultElement) { public static E getOnlyElement(Iterator traversal, E defaultElement) { if (!traversal.hasNext()) return defaultElement; - E result = traversal.next(); + final E result = traversal.next(); if (traversal.hasNext()) throw new IllegalArgumentException("Traversal contains more than 1 element: " + result + ", " + traversal.next()); return result; } @@ -429,10 +429,10 @@ public static void verifyElementOrder(Iterator elements, Stri Comparable previous = null; int count = 0; while (elements.hasNext()) { - Element element = elements.next(); - Comparable current = element.value(key); + final Element element = elements.next(); + final Comparable current = element.value(key); if (previous != null) { - int cmp = previous.compareTo(current); + final int cmp = previous.compareTo(current); assertTrue(previous + " <> " + current + " @ " + count, order == Order.ASC ? cmp <= 0 : cmp >= 0); } @@ -447,4 +447,13 @@ public static Stream asStream(final Iterator source) { return StreamSupport.stream(iterable.spliterator(),false); } + public JanusGraph getForceIndexGraph() throws BackendException { + final ModifiableConfiguration adjustedConfig = new ModifiableConfiguration(GraphDatabaseConfiguration.ROOT_NS,getConfiguration(), BasicConfiguration.Restriction.NONE); + adjustedConfig.set(GraphDatabaseConfiguration.FORCE_INDEX_USAGE, true); + final WriteConfiguration writeConfig = adjustedConfig.getConfiguration(); + TestGraphConfigs.applyOverrides(writeConfig); + Preconditions.checkNotNull(writeConfig); + return JanusGraphFactory.open(writeConfig); + } + } diff --git a/janusgraph-test/src/main/java/org/janusgraph/graphdb/JanusGraphIndexTest.java b/janusgraph-test/src/main/java/org/janusgraph/graphdb/JanusGraphIndexTest.java index f596bbcf2ce..1199a978e09 100644 --- a/janusgraph-test/src/main/java/org/janusgraph/graphdb/JanusGraphIndexTest.java +++ b/janusgraph-test/src/main/java/org/janusgraph/graphdb/JanusGraphIndexTest.java @@ -40,10 +40,12 @@ import org.janusgraph.core.schema.SchemaAction; import org.janusgraph.core.schema.SchemaStatus; import org.janusgraph.core.schema.JanusGraphIndex; +import org.janusgraph.core.schema.JanusGraphManagement; import org.janusgraph.core.util.ManagementUtil; import org.janusgraph.diskstorage.Backend; import org.janusgraph.diskstorage.BackendException; import org.janusgraph.diskstorage.configuration.ConfigElement; +import org.janusgraph.diskstorage.configuration.ModifiableConfiguration; import org.janusgraph.diskstorage.configuration.WriteConfiguration; import org.janusgraph.diskstorage.indexing.IndexFeatures; import org.janusgraph.diskstorage.indexing.IndexInformation; @@ -56,14 +58,21 @@ import org.janusgraph.graphdb.internal.ElementCategory; import org.janusgraph.graphdb.internal.Order; import org.janusgraph.graphdb.log.StandardTransactionLogProcessor; +import org.janusgraph.graphdb.query.condition.Not; import org.janusgraph.graphdb.types.ParameterType; import org.janusgraph.graphdb.types.StandardEdgeLabelMaker; import org.janusgraph.testcategory.BrittleTests; import org.janusgraph.testutil.TestGraphConfigs; +import org.apache.tinkerpop.gremlin.process.traversal.P; +import org.apache.tinkerpop.gremlin.process.traversal.dsl.graph.GraphTraversal; import org.apache.tinkerpop.gremlin.process.traversal.dsl.graph.GraphTraversalSource; +import org.apache.tinkerpop.gremlin.process.traversal.dsl.graph.__; +import org.apache.tinkerpop.gremlin.process.traversal.step.util.ElementValueComparator; +import org.apache.tinkerpop.gremlin.process.traversal.util.TraversalMetrics; import org.apache.tinkerpop.gremlin.structure.Direction; import org.apache.tinkerpop.gremlin.structure.Edge; import org.apache.tinkerpop.gremlin.structure.Element; +import org.apache.tinkerpop.gremlin.structure.Graph; import org.apache.tinkerpop.gremlin.structure.Vertex; import org.apache.tinkerpop.gremlin.structure.VertexProperty; import org.junit.Assert; @@ -101,8 +110,14 @@ /** * @author Matthias Broecheler (me@matthiasb.com) */ +@SuppressWarnings({"rawtypes", "unchecked"}) public abstract class JanusGraphIndexTest extends JanusGraphBaseTest { + private static final ElementValueComparator ORDER_AGE_DECR = new ElementValueComparator("age", org.apache.tinkerpop.gremlin.process.traversal.Order.decr); + private static final ElementValueComparator ORDER_AGE_INCR = new ElementValueComparator("age", org.apache.tinkerpop.gremlin.process.traversal.Order.incr); + private static final ElementValueComparator ORDER_LENGTH_DECR = new ElementValueComparator("length", org.apache.tinkerpop.gremlin.process.traversal.Order.decr); + private static final ElementValueComparator ORDER_LENGTH_INCR = new ElementValueComparator("length", org.apache.tinkerpop.gremlin.process.traversal.Order.incr); + public static final String INDEX = GraphOfTheGodsFactory.INDEX_NAME; public static final String VINDEX = "v" + INDEX; public static final String EINDEX = "e" + INDEX; @@ -175,7 +190,7 @@ public void testGraphOfTheGods() { public static void assertGraphOfTheGods(JanusGraph graphOfTheGods) { assertCount(12, graphOfTheGods.query().vertices()); assertCount(3, graphOfTheGods.query().has(LABEL_NAME, "god").vertices()); - JanusGraphVertex h = getOnlyVertex(graphOfTheGods.query().has("name", "hercules")); + final JanusGraphVertex h = getOnlyVertex(graphOfTheGods.query().has("name", "hercules")); assertEquals(30, h.value("age").intValue()); assertEquals("demigod", h.label()); assertCount(5, h.query().direction(Direction.BOTH).edges()); @@ -194,7 +209,7 @@ public void testClearStorage() throws Exception { final Backend backend = getBackend(config, false); assertStorageExists(backend, true); clearGraph(config); - try { backend.close(); } catch (Exception e) { /* Most backends do not support closing after clearing */} + try { backend.close(); } catch (final Exception e) { /* Most backends do not support closing after clearing */} try (final Backend newBackend = getBackend(config, false)) { assertStorageExists(newBackend, false); } @@ -210,7 +225,7 @@ private static void assertStorageExists(Backend backend, boolean exists) throws @Test public void testSimpleUpdate() { - PropertyKey name = makeKey("name", String.class); + final PropertyKey name = makeKey("name", String.class); makeLabel("knows"); mgmt.buildIndex("namev", Vertex.class).addKey(name).buildMixedIndex(INDEX); mgmt.buildIndex("namee", Edge.class).addKey(name).buildMixedIndex(INDEX); @@ -220,11 +235,11 @@ public void testSimpleUpdate() { Edge e = v.addEdge("knows", v, "name", "Hulu Bubab"); assertCount(1, tx.query().has("name", Text.CONTAINS, "marko").vertices()); assertCount(1, tx.query().has("name", Text.CONTAINS, "Hulu").edges()); - for (Vertex u : tx.getVertices()) assertEquals("Marko Rodriguez", u.value("name")); + for (final Vertex u : tx.getVertices()) assertEquals("Marko Rodriguez", u.value("name")); clopen(); assertCount(1, tx.query().has("name", Text.CONTAINS, "marko").vertices()); assertCount(1, tx.query().has("name", Text.CONTAINS, "Hulu").edges()); - for (Vertex u : tx.getVertices()) assertEquals("Marko Rodriguez", u.value("name")); + for (final Vertex u : tx.getVertices()) assertEquals("Marko Rodriguez", u.value("name")); v = getOnlyVertex(tx.query().has("name", Text.CONTAINS, "marko")); v.property(VertexProperty.Cardinality.single, "name", "Marko"); e = getOnlyEdge(v.query().direction(Direction.OUT)); @@ -232,12 +247,12 @@ public void testSimpleUpdate() { assertCount(1, tx.query().has("name", Text.CONTAINS, "marko").vertices()); assertCount(1, tx.query().has("name", Text.CONTAINS, "Rubu").edges()); assertCount(0, tx.query().has("name", Text.CONTAINS, "Hulu").edges()); - for (Vertex u : tx.getVertices()) assertEquals("Marko", u.value("name")); + for (final Vertex u : tx.getVertices()) assertEquals("Marko", u.value("name")); clopen(); assertCount(1, tx.query().has("name", Text.CONTAINS, "marko").vertices()); assertCount(1, tx.query().has("name", Text.CONTAINS, "Rubu").edges()); assertCount(0, tx.query().has("name", Text.CONTAINS, "Hulu").edges()); - for (Vertex u : tx.getVertices()) assertEquals("Marko", u.value("name")); + for (final Vertex u : tx.getVertices()) assertEquals("Marko", u.value("name")); } @Test @@ -245,11 +260,11 @@ public void testListUpdate() { if (!indexFeatures.supportsCardinality(Cardinality.LIST)) { return; } - PropertyKey name = makeKey("name", String.class); + final PropertyKey name = makeKey("name", String.class); if (!indexFeatures.supportsStringMapping(Mapping.TEXTSTRING)) { - + } - PropertyKey alias = mgmt.makePropertyKey("alias") .dataType(String.class).cardinality(Cardinality.LIST).make(); + final PropertyKey alias = mgmt.makePropertyKey("alias") .dataType(String.class).cardinality(Cardinality.LIST).make(); mgmt.buildIndex("namev", Vertex.class).addKey(name).addKey(alias, indexFeatures.supportsStringMapping(Mapping.TEXTSTRING) ?Mapping.TEXTSTRING.asParameter(): Mapping.DEFAULT.asParameter()).buildMixedIndex(INDEX); finishSchema(); JanusGraphVertex v = tx.addVertex("name", "Marko Rodriguez"); @@ -272,14 +287,14 @@ public void testListUpdate() { assertCount(1, tx.query().has("alias", Cmp.EQUAL, "mRodriguez").vertices()); } } - + @Test public void testSetUpdate() { if (!indexFeatures.supportsCardinality(Cardinality.SET)) { return; } - PropertyKey name = makeKey("name", String.class); - PropertyKey alias = mgmt.makePropertyKey("alias").dataType(String.class).cardinality(Cardinality.SET).make(); + final PropertyKey name = makeKey("name", String.class); + final PropertyKey alias = mgmt.makePropertyKey("alias").dataType(String.class).cardinality(Cardinality.SET).make(); mgmt.buildIndex("namev", Vertex.class).addKey(name).addKey(alias, indexFeatures.supportsStringMapping(Mapping.TEXTSTRING) ?Mapping.TEXTSTRING.asParameter(): Mapping.DEFAULT.asParameter()).buildMixedIndex(INDEX); finishSchema(); JanusGraphVertex v = tx.addVertex("name", "Marko Rodriguez"); @@ -307,27 +322,27 @@ public void testSetUpdate() { @Test public void testIndexing() throws InterruptedException { - PropertyKey text = makeKey("text", String.class); + final PropertyKey text = makeKey("text", String.class); createExternalVertexIndex(text, INDEX); createExternalEdgeIndex(text, INDEX); - PropertyKey location = makeKey("location", Geoshape.class); + final PropertyKey location = makeKey("location", Geoshape.class); createExternalVertexIndex(location, INDEX); createExternalEdgeIndex(location, INDEX); - PropertyKey boundary = makeKey("boundary", Geoshape.class); + final PropertyKey boundary = makeKey("boundary", Geoshape.class); mgmt.addIndexKey(getExternalIndex(Vertex.class,INDEX),boundary, Parameter.of("mapping", Mapping.PREFIX_TREE), Parameter.of("index-geo-dist-error-pct", 0.0025)); mgmt.addIndexKey(getExternalIndex(Edge.class,INDEX),boundary, Parameter.of("mapping", Mapping.PREFIX_TREE), Parameter.of("index-geo-dist-error-pct", 0.0025)); - PropertyKey time = makeKey("time", Long.class); + final PropertyKey time = makeKey("time", Long.class); createExternalVertexIndex(time, INDEX); createExternalEdgeIndex(time, INDEX); - PropertyKey category = makeKey("category", Integer.class); + final PropertyKey category = makeKey("category", Integer.class); mgmt.buildIndex("vcategory", Vertex.class).addKey(category).buildCompositeIndex(); mgmt.buildIndex("ecategory", Edge.class).addKey(category).buildCompositeIndex(); - PropertyKey group = makeKey("group", Byte.class); + final PropertyKey group = makeKey("group", Byte.class); createExternalVertexIndex(group, INDEX); createExternalEdgeIndex(group, INDEX); @@ -336,14 +351,14 @@ public void testIndexing() throws InterruptedException { finishSchema(); clopen(); - String[] words = {"world", "aurelius", "janusgraph", "graph"}; - int numCategories = 5; - int numGroups = 10; - double distance, offset; + final String[] words = {"world", "aurelius", "janusgraph", "graph"}; + final int numCategories = 5; + final int numGroups = 10; + double offset; int numV = 100; final int originalNumV = numV; for (int i = 0; i < numV; i++) { - JanusGraphVertex v = tx.addVertex(); + final JanusGraphVertex v = tx.addVertex(); v.property(VertexProperty.Cardinality.single, "uid", i); v.property(VertexProperty.Cardinality.single, "category", i % numCategories); v.property(VertexProperty.Cardinality.single, "group", i % numGroups); @@ -358,7 +373,7 @@ public void testIndexing() throws InterruptedException { v.property(VertexProperty.Cardinality.single, "boundary", Geoshape.polygon(Arrays.asList(new double[][] {{offset-0.1,offset-0.1},{offset+0.1,offset-0.1},{offset+0.1,offset+0.1},{offset-0.1,offset+0.1},{offset-0.1,offset-0.1}}))); } - Edge e = v.addEdge("knows", getVertex("uid", Math.max(0, i - 1))); + final Edge e = v.addEdge("knows", getVertex("uid", Math.max(0, i - 1))); e.property("text", "Vertex " + words[i % words.length]); e.property("time", i); e.property("category", i % numCategories); @@ -383,7 +398,7 @@ public void testIndexing() throws InterruptedException { try { checkIndexingCounts(words, numV, originalNumV, true); status = 0; - } catch (AssertionError e) { + } catch (final AssertionError e) { if (retry >= RETRY_COUNT-1) throw e; Thread.sleep(RETRY_INTERVAL); } @@ -391,7 +406,7 @@ public void testIndexing() throws InterruptedException { newTx(); - int numDelete = 12; + final int numDelete = 12; for (int i = numV - numDelete; i < numV; i++) { getVertex("uid", i).remove(); } @@ -409,9 +424,9 @@ private void checkIndexingCounts(String[] words, int numV, int originalNumV, boo //Test ordering if (checkOrder) { - for (String orderKey : new String[]{"time", "category"}) { - for (Order order : Order.values()) { - for (JanusGraphQuery traversal : ImmutableList.of( + for (final String orderKey : new String[]{"time", "category"}) { + for (final Order order : Order.values()) { + for (final JanusGraphQuery traversal : ImmutableList.of( tx.query().has("text", Text.CONTAINS, word).orderBy(orderKey, order.getTP()), tx.query().has("text", Text.CONTAINS, word).orderBy(orderKey, order.getTP()) )) { @@ -439,8 +454,8 @@ private void checkIndexingCounts(String[] words, int numV, int originalNumV, boo assertCount(4, tx.query().has("category", 1).interval("time", 10, 28).edges()); assertCount(5, tx.query().has("time", Cmp.GREATER_THAN_EQUAL, 10).has("time", Cmp.LESS_THAN, 30).has("text", Text.CONTAINS, words[0]).vertices()); - double offset = (19 * 50.0 / originalNumV); - double distance = Geoshape.point(0.0, 0.0).getPoint().distance(Geoshape.point(offset, offset).getPoint()) + 20; + final double offset = (19 * 50.0 / originalNumV); + final double distance = Geoshape.point(0.0, 0.0).getPoint().distance(Geoshape.point(offset, offset).getPoint()) + 20; assertCount(5, tx.query().has("location", Geo.INTERSECT, Geoshape.circle(0.0, 0.0, distance)).has("text", Text.CONTAINS, words[0]).vertices()); assertCount(5, tx.query().has("boundary", Geo.INTERSECT, Geoshape.circle(0.0, 0.0, distance)).has("text", Text.CONTAINS, words[0]).vertices()); @@ -453,16 +468,16 @@ private void checkIndexingCounts(String[] words, int numV, int originalNumV, boo */ @Test public void testBooleanIndexing() { - PropertyKey name = makeKey("visible", Boolean.class); + final PropertyKey name = makeKey("visible", Boolean.class); mgmt.buildIndex("booleanIndex", Vertex.class). addKey(name).buildMixedIndex(INDEX); finishSchema(); clopen(); - JanusGraphVertex v1 = graph.addVertex(); + final JanusGraphVertex v1 = graph.addVertex(); v1.property("visible", true); - JanusGraphVertex v2 = graph.addVertex(); + final JanusGraphVertex v2 = graph.addVertex(); v2.property("visible", false); assertCount(2, graph.vertices()); @@ -485,16 +500,16 @@ public void testBooleanIndexing() { */ @Test public void testDateIndexing() { - PropertyKey name = makeKey("date", Date.class); + final PropertyKey name = makeKey("date", Date.class); mgmt.buildIndex("dateIndex", Vertex.class). addKey(name).buildMixedIndex(INDEX); finishSchema(); clopen(); - JanusGraphVertex v1 = graph.addVertex(); + final JanusGraphVertex v1 = graph.addVertex(); v1.property("date", new Date(1)); - JanusGraphVertex v2 = graph.addVertex(); + final JanusGraphVertex v2 = graph.addVertex(); v2.property("date", new Date(2000)); @@ -522,18 +537,18 @@ public void testDateIndexing() { */ @Test public void testInstantIndexing() { - PropertyKey name = makeKey("instant", Instant.class); + final PropertyKey name = makeKey("instant", Instant.class); mgmt.buildIndex("instantIndex", Vertex.class). addKey(name).buildMixedIndex(INDEX); finishSchema(); clopen(); Instant firstTimestamp = Instant.ofEpochMilli(1); - Instant secondTimestamp = Instant.ofEpochMilli(2000); + final Instant secondTimestamp = Instant.ofEpochMilli(2000); JanusGraphVertex v1 = graph.addVertex(); v1.property("instant", firstTimestamp); - JanusGraphVertex v2 = graph.addVertex(); + final JanusGraphVertex v2 = graph.addVertex(); v2.property("instant", secondTimestamp); testInstant(firstTimestamp, secondTimestamp, v1, v2); @@ -548,7 +563,7 @@ public void testInstantIndexing() { try { assertEquals(v1, getOnlyVertex(graph.query().has("instant", Cmp.EQUAL, firstTimestamp))); Assert.fail("Should have failed to update the index"); - } catch (Exception ignored) { + } catch (final Exception ignored) { } } @@ -578,19 +593,19 @@ private void testInstant(Instant firstTimestamp, Instant secondTimestamp, JanusG */ @Test public void testUUIDIndexing() { - PropertyKey name = makeKey("uid", UUID.class); + final PropertyKey name = makeKey("uid", UUID.class); mgmt.buildIndex("uuidIndex", Vertex.class). addKey(name).buildMixedIndex(INDEX); finishSchema(); clopen(); - UUID uid1 = UUID.randomUUID(); - UUID uid2 = UUID.randomUUID(); + final UUID uid1 = UUID.randomUUID(); + final UUID uid2 = UUID.randomUUID(); - JanusGraphVertex v1 = graph.addVertex(); + final JanusGraphVertex v1 = graph.addVertex(); v1.property("uid", uid1); - JanusGraphVertex v2 = graph.addVertex(); + final JanusGraphVertex v2 = graph.addVertex(); v2.property("uid", uid2); assertCount(2, graph.query().vertices()); @@ -658,19 +673,19 @@ public void testConditionalIndexing() { //Already exists mgmt.buildIndex("index2", Vertex.class).addKey(weight).buildMixedIndex(INDEX); fail(); - } catch (IllegalArgumentException ignored) { + } catch (final IllegalArgumentException ignored) { } try { //Already exists mgmt.buildIndex("index2", Vertex.class).addKey(weight).buildCompositeIndex(); fail(); - } catch (IllegalArgumentException ignored) { + } catch (final IllegalArgumentException ignored) { } try { //Key is already added mgmt.addIndexKey(index2, weight); fail(); - } catch (IllegalArgumentException ignored) { + } catch (final IllegalArgumentException ignored) { } finishSchema(); @@ -704,19 +719,19 @@ public void testConditionalIndexing() { //Already exists mgmt.buildIndex("index2", Vertex.class).addKey(weight).buildMixedIndex(INDEX); fail(); - } catch (IllegalArgumentException ignored) { + } catch (final IllegalArgumentException ignored) { } try { //Already exists mgmt.buildIndex("index2", Vertex.class).addKey(weight).buildCompositeIndex(); fail(); - } catch (IllegalArgumentException ignored) { + } catch (final IllegalArgumentException ignored) { } try { //Key is already added mgmt.addIndexKey(index2, weight); fail(); - } catch (IllegalArgumentException ignored) { + } catch (final IllegalArgumentException ignored) { } @@ -725,14 +740,14 @@ public void testConditionalIndexing() { final int numV = 200; - String[] strings = {"houseboat", "humanoid", "differential", "extraordinary"}; - String[] stringsTwo = new String[strings.length]; + final String[] strings = {"houseboat", "humanoid", "differential", "extraordinary"}; + final String[] stringsTwo = new String[strings.length]; for (int i = 0; i < strings.length; i++) stringsTwo[i] = strings[i] + " " + strings[i]; final int modulo = 5; assert numV % (modulo * strings.length * 2) == 0; for (int i = 0; i < numV; i++) { - JanusGraphVertex v = tx.addVertex(i % 2 == 0 ? "person" : "org"); + final JanusGraphVertex v = tx.addVertex(i % 2 == 0 ? "person" : "org"); v.property("name", strings[i % strings.length]); v.property("text", strings[i % strings.length]); v.property("weight", (i % modulo) + 0.5); @@ -798,27 +813,27 @@ public void testConditionalIndexing() { @Test public void testCompositeAndMixedIndexing() { - PropertyKey name = makeKey("name", String.class); - PropertyKey weight = makeKey("weight", Double.class); - PropertyKey text = makeKey("text", String.class); + final PropertyKey name = makeKey("name", String.class); + final PropertyKey weight = makeKey("weight", Double.class); + final PropertyKey text = makeKey("text", String.class); makeKey("flag", Boolean.class); - JanusGraphIndex composite = mgmt.buildIndex("composite", Vertex.class).addKey(name).addKey(weight).buildCompositeIndex(); - JanusGraphIndex mixed = mgmt.buildIndex("mixed", Vertex.class).addKey(weight) + final JanusGraphIndex composite = mgmt.buildIndex("composite", Vertex.class).addKey(name).addKey(weight).buildCompositeIndex(); + final JanusGraphIndex mixed = mgmt.buildIndex("mixed", Vertex.class).addKey(weight) .addKey(text, getTextMapping()).buildMixedIndex(INDEX); mixed.name(); composite.name(); finishSchema(); final int numV = 100; - String[] strings = {"houseboat", "humanoid", "differential", "extraordinary"}; - String[] stringsTwo = new String[strings.length]; + final String[] strings = {"houseboat", "humanoid", "differential", "extraordinary"}; + final String[] stringsTwo = new String[strings.length]; for (int i = 0; i < strings.length; i++) stringsTwo[i] = strings[i] + " " + strings[i]; final int modulo = 5; final int divisor = modulo * strings.length; for (int i = 0; i < numV; i++) { - JanusGraphVertex v = tx.addVertex(); + final JanusGraphVertex v = tx.addVertex(); v.property("name", strings[i % strings.length]); v.property("text", strings[i % strings.length]); v.property("weight", (i % modulo) + 0.5); @@ -871,15 +886,15 @@ public void testCompositeAndMixedIndexing() { private void setupChainGraph(int numV, String[] strings, boolean sameNameMapping) { clopen(option(INDEX_NAME_MAPPING, INDEX), sameNameMapping); - JanusGraphIndex vindex = getExternalIndex(Vertex.class, INDEX); - JanusGraphIndex eindex = getExternalIndex(Edge.class, INDEX); - JanusGraphIndex pindex = getExternalIndex(JanusGraphVertexProperty.class, INDEX); - PropertyKey name = makeKey("name", String.class); + final JanusGraphIndex vindex = getExternalIndex(Vertex.class, INDEX); + final JanusGraphIndex eindex = getExternalIndex(Edge.class, INDEX); + final JanusGraphIndex pindex = getExternalIndex(JanusGraphVertexProperty.class, INDEX); + final PropertyKey name = makeKey("name", String.class); mgmt.addIndexKey(vindex, name, getStringMapping()); mgmt.addIndexKey(eindex, name, getStringMapping()); mgmt.addIndexKey(pindex, name, getStringMapping(), Parameter.of("mapped-name", "xstr")); - PropertyKey text = makeKey("text", String.class); + final PropertyKey text = makeKey("text", String.class); mgmt.addIndexKey(vindex, text, getTextMapping(), Parameter.of("mapped-name", "xtext")); mgmt.addIndexKey(eindex, text, getTextMapping()); mgmt.addIndexKey(pindex, text, getTextMapping()); @@ -888,7 +903,7 @@ private void setupChainGraph(int numV, String[] strings, boolean sameNameMapping finishSchema(); JanusGraphVertex previous = null; for (int i = 0; i < numV; i++) { - JanusGraphVertex v = graph.addVertex("name", strings[i % strings.length], "text", strings[i % strings.length]); + final JanusGraphVertex v = graph.addVertex("name", strings[i % strings.length], "text", strings[i % strings.length]); v.addEdge("knows", previous == null ? v : previous, "name", strings[i % strings.length], "text", strings[i % strings.length]); v.property("uid", "v" + i, @@ -902,8 +917,8 @@ private void setupChainGraph(int numV, String[] strings, boolean sameNameMapping */ @Test public void testIndexParameters() { - int numV = 1000; - String[] strings = {"Uncle Berry has a farm", "and on his farm he has five ducks", "ducks are beautiful animals", "the sky is very blue today"}; + final int numV = 1000; + final String[] strings = {"Uncle Berry has a farm", "and on his farm he has five ducks", "ducks are beautiful animals", "the sky is very blue today"}; setupChainGraph(numV, strings, false); evaluateQuery(graph.query().has("text", Text.CONTAINS, "ducks"), @@ -1025,8 +1040,8 @@ public void testIndexParameters() { public void testRawQueries() { if (!supportsLuceneStyleQueries()) return; - int numV = 1000; - String[] strings = {"Uncle Berry has a farm", "and on his farm he has five ducks", "ducks are beautiful animals", "the sky is very blue today"}; + final int numV = 1000; + final String[] strings = {"Uncle Berry has a farm", "and on his farm he has five ducks", "ducks are beautiful animals", "the sky is very blue today"}; setupChainGraph(numV, strings, true); clopen(); @@ -1096,7 +1111,7 @@ public void testRawQueriesWithParameters() throws Exception { Parameter desc_sort_p = null; // ElasticSearch and Solr have different formats for sort parameters - String backend = readConfig.get(INDEX_BACKEND, INDEX); + final String backend = readConfig.get(INDEX_BACKEND, INDEX); switch (backend) { case "elasticsearch": final Map sortAsc = new HashMap<>(); @@ -1122,9 +1137,9 @@ public void testRawQueriesWithParameters() throws Exception { mgmt.buildIndex("store1", Vertex.class).addKey(field1Key).buildMixedIndex(INDEX); mgmt.commit(); - JanusGraphVertex v1 = tx.addVertex(); - JanusGraphVertex v2 = tx.addVertex(); - JanusGraphVertex v3 = tx.addVertex(); + final JanusGraphVertex v1 = tx.addVertex(); + final JanusGraphVertex v2 = tx.addVertex(); + final JanusGraphVertex v3 = tx.addVertex(); v1.property("field1", "Hello Hello Hello Hello Hello Hello Hello Hello world"); v2.property("field1", "Hello blue and yellow meet green"); @@ -1148,8 +1163,8 @@ public void testRawQueriesWithParameters() throws Exception { public void testDualMapping() { if (!indexFeatures.supportsStringMapping(Mapping.TEXTSTRING)) return; - PropertyKey name = makeKey("name", String.class); - JanusGraphIndex mixed = mgmt.buildIndex("mixed", Vertex.class).addKey(name, Mapping.TEXTSTRING.asParameter()).buildMixedIndex(INDEX); + final PropertyKey name = makeKey("name", String.class); + final JanusGraphIndex mixed = mgmt.buildIndex("mixed", Vertex.class).addKey(name, Mapping.TEXTSTRING.asParameter()).buildMixedIndex(INDEX); mixed.name(); finishSchema(); @@ -1216,11 +1231,11 @@ public void testIndexReplay() throws Exception { , option(TestMockIndexProvider.INDEX_MOCK_FAILADD, INDEX), true ); - PropertyKey name = mgmt.makePropertyKey("name").dataType(String.class).make(); - PropertyKey age = mgmt.makePropertyKey("age").dataType(Integer.class).make(); + final PropertyKey name = mgmt.makePropertyKey("name").dataType(String.class).make(); + final PropertyKey age = mgmt.makePropertyKey("age").dataType(Integer.class).make(); mgmt.buildIndex("mi", Vertex.class).addKey(name, getTextMapping()).addKey(age).buildMixedIndex(INDEX); finishSchema(); - Vertex vs[] = new JanusGraphVertex[4]; + final Vertex vs[] = new JanusGraphVertex[4]; vs[0] = tx.addVertex("name", "Big Boy Bobson", "age", 55); newTx(); @@ -1245,12 +1260,12 @@ public void testIndexReplay() throws Exception { /* Transaction Recovery */ - TransactionRecovery recovery = JanusGraphFactory.startTransactionRecovery(graph, startTime); + final TransactionRecovery recovery = JanusGraphFactory.startTransactionRecovery(graph, startTime); //wait Thread.sleep(12000L); recovery.shutdown(); - long[] recoveryStats = ((StandardTransactionLogProcessor) recovery).getStatistics(); + final long[] recoveryStats = ((StandardTransactionLogProcessor) recovery).getStatistics(); clopen(); @@ -1276,7 +1291,7 @@ public void testIndexReplay() throws Exception { @Test public void testIndexUpdatesWithoutReindex() throws InterruptedException, ExecutionException { - Object[] settings = new Object[]{option(LOG_SEND_DELAY, MANAGEMENT_LOG), Duration.ofMillis(0), + final Object[] settings = new Object[]{option(LOG_SEND_DELAY, MANAGEMENT_LOG), Duration.ofMillis(0), option(KCVSLog.LOG_READ_LAG_TIME, MANAGEMENT_LOG), Duration.ofMillis(50), option(LOG_READ_INTERVAL, MANAGEMENT_LOG), Duration.ofMillis(250) }; @@ -1289,7 +1304,7 @@ public void testIndexUpdatesWithoutReindex() throws InterruptedException, Execut //Creates types and index only two keys key mgmt.makePropertyKey("time").dataType(Integer.class).make(); - PropertyKey text = mgmt.makePropertyKey("text").dataType(String.class).make(); + final PropertyKey text = mgmt.makePropertyKey("text").dataType(String.class).make(); mgmt.makePropertyKey("height").dataType(Double.class).make(); if (indexFeatures.supportsCardinality(Cardinality.LIST)) { @@ -1323,7 +1338,7 @@ public void testIndexUpdatesWithoutReindex() throws InterruptedException, Execut //Add another key to index ------------------------------------------------------ finishSchema(); - PropertyKey time = mgmt.getPropertyKey("time"); + final PropertyKey time = mgmt.getPropertyKey("time"); mgmt.addIndexKey(mgmt.getGraphIndex("theIndex"), time, getFieldMap(time)); finishSchema(); newTx(); @@ -1368,10 +1383,10 @@ public void testIndexUpdatesWithoutReindex() throws InterruptedException, Execut //Add another key to index ------------------------------------------------------ finishSchema(); - PropertyKey height = mgmt.getPropertyKey("height"); + final PropertyKey height = mgmt.getPropertyKey("height"); mgmt.addIndexKey(mgmt.getGraphIndex("theIndex"), height); if (indexFeatures.supportsCardinality(Cardinality.LIST)) { - PropertyKey phone = mgmt.getPropertyKey("phone"); + final PropertyKey phone = mgmt.getPropertyKey("phone"); mgmt.addIndexKey(mgmt.getGraphIndex("theIndex"), phone, new Parameter("mapping", Mapping.STRING)); } finishSchema(); @@ -1388,7 +1403,7 @@ public void testIndexUpdatesWithoutReindex() throws InterruptedException, Execut finishSchema(); JanusGraphIndex index = mgmt.getGraphIndex("theIndex"); - for (PropertyKey key : index.getFieldKeys()) { + for (final PropertyKey key : index.getFieldKeys()) { assertEquals(SchemaStatus.ENABLED, index.getIndexStatus(key)); } @@ -1448,7 +1463,7 @@ public void testIndexUpdatesWithoutReindex() throws InterruptedException, Execut finishSchema(); index = mgmt.getGraphIndex("theIndex"); - for (PropertyKey key : index.getFieldKeys()) { + for (final PropertyKey key : index.getFieldKeys()) { assertEquals(SchemaStatus.DISABLED, index.getIndexStatus(key)); } @@ -1461,8 +1476,8 @@ public void testIndexUpdatesWithoutReindex() throws InterruptedException, Execut private void addVertex(int time, String text, double height, String[] phones) { newTx(); - JanusGraphVertex v = tx.addVertex("text", text, "time", time, "height", height); - for (String phone : phones) { + final JanusGraphVertex v = tx.addVertex("text", text, "time", time, "height", height); + for (final String phone : phones) { v.property("phone", phone); } @@ -1481,11 +1496,11 @@ public void testVertexTTLWithMixedIndices() throws Exception { return; } - PropertyKey name = makeKey("name", String.class); - PropertyKey time = makeKey("time", Long.class); - PropertyKey text = makeKey("text", String.class); + final PropertyKey name = makeKey("name", String.class); + final PropertyKey time = makeKey("time", Long.class); + final PropertyKey text = makeKey("text", String.class); - VertexLabel event = mgmt.makeVertexLabel("event").setStatic().make(); + final VertexLabel event = mgmt.makeVertexLabel("event").setStatic().make(); final int eventTTLSeconds = (int) TestGraphConfigs.getTTL(TimeUnit.SECONDS); mgmt.setTTL(event, Duration.ofSeconds(eventTTLSeconds)); @@ -1502,12 +1517,12 @@ public void testVertexTTLWithMixedIndices() throws Exception { JanusGraphVertex v1 = tx.addVertex("event"); v1.property(VertexProperty.Cardinality.single, "name", "first event"); v1.property(VertexProperty.Cardinality.single, "text", "this text will help to identify the first event"); - long time1 = System.currentTimeMillis(); + final long time1 = System.currentTimeMillis(); v1.property(VertexProperty.Cardinality.single, "time", time1); JanusGraphVertex v2 = tx.addVertex("event"); v2.property(VertexProperty.Cardinality.single, "name", "second event"); v2.property(VertexProperty.Cardinality.single, "text", "this text won't match"); - long time2 = time1 + 1; + final long time2 = time1 + 1; v2.property(VertexProperty.Cardinality.single, "time", time2); evaluateQuery(tx.query().has("name", "first event").orderBy("time", decr), @@ -1517,8 +1532,8 @@ public void testVertexTTLWithMixedIndices() throws Exception { clopen(); - Object v1Id = v1.id(); - Object v2Id = v2.id(); + final Object v1Id = v1.id(); + final Object v2Id = v2.id(); evaluateQuery(tx.query().has("name", "first event").orderBy("time", decr), ElementCategory.VERTEX, 1, new boolean[]{true, true}, tx.getPropertyKey("time"), Order.DESC, "index1"); @@ -1554,11 +1569,11 @@ public void testEdgeTTLWithMixedIndices() throws Exception { return; } - PropertyKey name = mgmt.makePropertyKey("name").dataType(String.class).make(); - PropertyKey text = mgmt.makePropertyKey("text").dataType(String.class).make(); - PropertyKey time = makeKey("time", Long.class); + final PropertyKey name = mgmt.makePropertyKey("name").dataType(String.class).make(); + final PropertyKey text = mgmt.makePropertyKey("text").dataType(String.class).make(); + final PropertyKey time = makeKey("time", Long.class); - EdgeLabel label = mgmt.makeEdgeLabel("likes").make(); + final EdgeLabel label = mgmt.makeEdgeLabel("likes").make(); final int likesTTLSeconds = (int) TestGraphConfigs.getTTL(TimeUnit.SECONDS); mgmt.setTTL(label, Duration.ofSeconds(likesTTLSeconds)); @@ -1574,16 +1589,16 @@ public void testEdgeTTLWithMixedIndices() throws Exception { JanusGraphVertex v1 = tx.addVertex(), v2 = tx.addVertex(), v3 = tx.addVertex(); Edge e1 = v1.addEdge("likes", v2, "name", "v1 likes v2", "text", "this will help to identify the edge"); - long time1 = System.currentTimeMillis(); + final long time1 = System.currentTimeMillis(); e1.property("time", time1); Edge e2 = v2.addEdge("likes", v3, "name", "v2 likes v3", "text", "this won't match anything"); - long time2 = time1 + 1; + final long time2 = time1 + 1; e2.property("time", time2); tx.commit(); clopen(); - Object e1Id = e1.id(); + final Object e1Id = e1.id(); e2.id(); evaluateQuery(tx.query().has("text", Text.CONTAINS, "help").has(LABEL_NAME, "likes"), @@ -1664,13 +1679,13 @@ public void testDeleteVertexThenModifyProperty() throws BackendException { @Test public void testIndexQueryWithScore() throws InterruptedException { - PropertyKey textKey = mgmt.makePropertyKey("text").dataType(String.class).make(); + final PropertyKey textKey = mgmt.makePropertyKey("text").dataType(String.class).make(); mgmt.buildIndex("store1", Vertex.class).addKey(textKey).buildMixedIndex(INDEX); mgmt.commit(); - JanusGraphVertex v1 = tx.addVertex(); - JanusGraphVertex v2 = tx.addVertex(); - JanusGraphVertex v3 = tx.addVertex(); + final JanusGraphVertex v1 = tx.addVertex(); + final JanusGraphVertex v2 = tx.addVertex(); + final JanusGraphVertex v3 = tx.addVertex(); v1.property("text", "Hello Hello Hello Hello Hello Hello Hello Hello world"); v2.property("text", "Hello abab abab fsdfsd sfdfsd sdffs fsdsdf fdf fsdfsd aera fsad abab abab fsdfsd sfdf"); @@ -1690,12 +1705,12 @@ public void testIndexQueryWithScore() throws InterruptedException { // which (in case of Solr) spans multiple conditions such as AND(AND(name:was, name:here)) // so we need to make sure that we don't apply AND twice. public void testContainsWithMultipleValues() throws Exception { - PropertyKey name = makeKey("name", String.class); + final PropertyKey name = makeKey("name", String.class); mgmt.buildIndex("store1", Vertex.class).addKey(name).buildMixedIndex(INDEX); mgmt.commit(); - JanusGraphVertex v1 = tx.addVertex(); + final JanusGraphVertex v1 = tx.addVertex(); v1.property("name", "hercules was here"); tx.commit(); @@ -1727,20 +1742,20 @@ private void testNestedWrites(String initialValue, String updatedValue) throws B final String propName = "foo"; // Write schema and one vertex - PropertyKey prop = makeKey(propName, String.class); + final PropertyKey prop = makeKey(propName, String.class); createExternalVertexIndex(prop, INDEX); finishSchema(); - JanusGraphVertex v = graph.addVertex(); + final JanusGraphVertex v = graph.addVertex(); if (null != initialValue) v.property(VertexProperty.Cardinality.single, propName, initialValue); graph.tx().commit(); - Object id = v.id(); + final Object id = v.id(); // Open two transactions and modify the same vertex - JanusGraphTransaction vertexDeleter = graph.newTransaction(); - JanusGraphTransaction propDeleter = graph.newTransaction(); + final JanusGraphTransaction vertexDeleter = graph.newTransaction(); + final JanusGraphTransaction propDeleter = graph.newTransaction(); getV(vertexDeleter, id).remove(); if (null == updatedValue) @@ -1766,14 +1781,14 @@ private void testNestedWrites(String initialValue, String updatedValue) throws B @Test public void testWidcardQuery() { if (supportsWildcardQuery()) { - PropertyKey p1 = makeKey("p1", String.class); - PropertyKey p2 = makeKey("p2", String.class); + final PropertyKey p1 = makeKey("p1", String.class); + final PropertyKey p2 = makeKey("p2", String.class); mgmt.buildIndex("mixedIndex", Vertex.class).addKey(p1).addKey(p2).buildMixedIndex(INDEX); finishSchema(); clopen(); - JanusGraphVertex v1 = graph.addVertex(); + final JanusGraphVertex v1 = graph.addVertex(); v1.property("p1", "test1"); v1.property("p2", "test2"); @@ -1806,11 +1821,11 @@ public void testSetIndexing() { private void testIndexing(Cardinality cardinality) { if (supportsCollections()) { - PropertyKey stringProperty = mgmt.makePropertyKey("name").dataType(String.class).cardinality(cardinality).make(); - PropertyKey intProperty = mgmt.makePropertyKey("age").dataType(Integer.class).cardinality(cardinality).make(); - PropertyKey longProperty = mgmt.makePropertyKey("long").dataType(Long.class).cardinality(cardinality).make(); - PropertyKey uuidProperty = mgmt.makePropertyKey("uuid").dataType(UUID.class).cardinality(cardinality).make(); - PropertyKey geopointProperty = mgmt.makePropertyKey("geopoint").dataType(Geoshape.class).cardinality(cardinality).make(); + final PropertyKey stringProperty = mgmt.makePropertyKey("name").dataType(String.class).cardinality(cardinality).make(); + final PropertyKey intProperty = mgmt.makePropertyKey("age").dataType(Integer.class).cardinality(cardinality).make(); + final PropertyKey longProperty = mgmt.makePropertyKey("long").dataType(Long.class).cardinality(cardinality).make(); + final PropertyKey uuidProperty = mgmt.makePropertyKey("uuid").dataType(UUID.class).cardinality(cardinality).make(); + final PropertyKey geopointProperty = mgmt.makePropertyKey("geopoint").dataType(Geoshape.class).cardinality(cardinality).make(); mgmt.buildIndex("collectionIndex", Vertex.class).addKey(stringProperty, getStringMapping()).addKey(intProperty).addKey(longProperty).addKey(uuidProperty).addKey(geopointProperty).buildMixedIndex(INDEX); finishSchema(); @@ -1818,19 +1833,19 @@ private void testIndexing(Cardinality cardinality) { testCollection(cardinality, "age", 1, 2); testCollection(cardinality, "long", 1L, 2L); testCollection(cardinality, "geopoint", Geoshape.point(1.0, 1.0), Geoshape.point(2.0, 2.0)); - String backend = readConfig.get(INDEX_BACKEND, INDEX); - // Solr 6 has issues processing UUIDs with Multivalues + final String backend = readConfig.get(INDEX_BACKEND, INDEX); + // Solr 6 has issues processing UUIDs with Multivalues // https://issues.apache.org/jira/browse/SOLR-11264 if (!"solr".equals(backend)) { testCollection(cardinality, "uuid", UUID.randomUUID(), UUID.randomUUID()); } } else { try { - PropertyKey stringProperty = mgmt.makePropertyKey("name").dataType(String.class).cardinality(cardinality).make(); + final PropertyKey stringProperty = mgmt.makePropertyKey("name").dataType(String.class).cardinality(cardinality).make(); //This should throw an exception mgmt.buildIndex("collectionIndex", Vertex.class).addKey(stringProperty, getStringMapping()).buildMixedIndex(INDEX); Assert.fail("Should have thrown an exception"); - } catch (JanusGraphException ignored) { + } catch (final JanusGraphException ignored) { } } @@ -1912,7 +1927,7 @@ private void testCollection(Cardinality cardinality, String property, Object val clopen(); // Flush the index g = graph.traversal(); v1 = g.addV().property(property, value1).property(property, value2).next(); - Vertex v2 = g.addV().property(property, value1).property(property, value2).next(); + g.addV().property(property, value1).property(property, value2).next(); clopen(); // Flush the index g = graph.traversal(); assertEquals(2, g.V().has(property, value1).toList().size()); @@ -1924,9 +1939,9 @@ private void testCollection(Cardinality cardinality, String property, Object val } private void testGeo(int i, int origNumV, int numV, String geoPointProperty, String geoShapeProperty) { - double offset = (i * 50.0 / origNumV); - double bufferKm = 20; - double distance = Geoshape.point(0.0, 0.0).getPoint().distance(Geoshape.point(offset, offset).getPoint()) + bufferKm; + final double offset = (i * 50.0 / origNumV); + final double bufferKm = 20; + final double distance = Geoshape.point(0.0, 0.0).getPoint().distance(Geoshape.point(offset, offset).getPoint()) + bufferKm; assertCount(i + 1, tx.query().has(geoPointProperty, Geo.WITHIN, Geoshape.circle(0.0, 0.0, distance)).vertices()); assertCount(i + 1, tx.query().has(geoPointProperty, Geo.WITHIN, Geoshape.circle(0.0, 0.0, distance)).edges()); @@ -1948,10 +1963,10 @@ private void testGeo(int i, int origNumV, int numV, String geoPointProperty, Str assertCount(i % 2, tx.query().has(geoShapeProperty, Geo.CONTAINS, Geoshape.point(-offset, -offset)).edges()); } - double buffer = bufferKm/111.; - double min = -Math.abs(offset); - double max = Math.abs(offset); - Geoshape bufferedBox = Geoshape.box(min-buffer, min-buffer, max+buffer, max+buffer); + final double buffer = bufferKm/111.; + final double min = -Math.abs(offset); + final double max = Math.abs(offset); + final Geoshape bufferedBox = Geoshape.box(min-buffer, min-buffer, max+buffer, max+buffer); assertCount(i + 1, tx.query().has(geoPointProperty, Geo.WITHIN, bufferedBox).vertices()); assertCount(i + 1, tx.query().has(geoPointProperty, Geo.WITHIN, bufferedBox).edges()); assertCount(i + 1, tx.query().has(geoPointProperty, Geo.INTERSECT, bufferedBox).vertices()); @@ -1959,7 +1974,7 @@ private void testGeo(int i, int origNumV, int numV, String geoPointProperty, Str assertCount(numV-(i + 1), tx.query().has(geoPointProperty, Geo.DISJOINT, bufferedBox).vertices()); assertCount(numV-(i + 1), tx.query().has(geoPointProperty, Geo.DISJOINT, bufferedBox).edges()); if (i > 0) { - Geoshape exactBox = Geoshape.box(min, min, max, max); + final Geoshape exactBox = Geoshape.box(min, min, max, max); assertCount(i, tx.query().has(geoShapeProperty, Geo.WITHIN, exactBox).vertices()); assertCount(i, tx.query().has(geoShapeProperty, Geo.WITHIN, exactBox).edges()); } @@ -1968,10 +1983,10 @@ private void testGeo(int i, int origNumV, int numV, String geoPointProperty, Str assertCount(numV-(i + 1), tx.query().has(geoShapeProperty, Geo.DISJOINT, bufferedBox).vertices()); assertCount(numV-(i + 1), tx.query().has(geoShapeProperty, Geo.DISJOINT, bufferedBox).edges()); - Geoshape bufferedPoly = Geoshape.polygon(Arrays.asList(new double[][] + final Geoshape bufferedPoly = Geoshape.polygon(Arrays.asList(new double[][] {{min-buffer,min-buffer},{max+buffer,min-buffer},{max+buffer,max+buffer},{min-buffer,max+buffer},{min-buffer,min-buffer}})); if (i > 0) { - Geoshape exactPoly = Geoshape.polygon(Arrays.asList(new double[][] + final Geoshape exactPoly = Geoshape.polygon(Arrays.asList(new double[][] {{min,min},{max,min},{max,max},{min,max},{min,min}})); assertCount(i, tx.query().has(geoShapeProperty, Geo.WITHIN, exactPoly).vertices()); assertCount(i, tx.query().has(geoShapeProperty, Geo.WITHIN, exactPoly).edges()); @@ -1984,22 +1999,201 @@ private void testGeo(int i, int origNumV, int numV, String geoPointProperty, Str @Test public void shouldAwaitMultipleStatuses() throws InterruptedException, ExecutionException { - PropertyKey key1 = makeKey("key1", String.class); - JanusGraphIndex index = mgmt.buildIndex("randomMixedIndex", Vertex.class).addKey(key1).buildMixedIndex(INDEX); + final PropertyKey key1 = makeKey("key1", String.class); + final JanusGraphIndex index = mgmt.buildIndex("randomMixedIndex", Vertex.class).addKey(key1).buildMixedIndex(INDEX); if (index.getIndexStatus(key1) == SchemaStatus.INSTALLED) { mgmt.updateIndex(mgmt.getGraphIndex("randomMixedIndex"), SchemaAction.REGISTER_INDEX).get(); mgmt.updateIndex(mgmt.getGraphIndex("randomMixedIndex"), SchemaAction.ENABLE_INDEX).get(); } else if (index.getIndexStatus(key1) == SchemaStatus.REGISTERED) { mgmt.updateIndex(mgmt.getGraphIndex("randomMixedIndex"), SchemaAction.ENABLE_INDEX).get(); } - PropertyKey key2 = makeKey("key2", String.class); + final PropertyKey key2 = makeKey("key2", String.class); mgmt.addIndexKey(index, key2); mgmt.commit(); //key1 now has status ENABLED, let's ensure we can watch for REGISTERED and ENABLED try { ManagementSystem.awaitGraphIndexStatus(graph, "randomMixedIndex").status(SchemaStatus.REGISTERED, SchemaStatus.ENABLED).call(); - } catch (Exception e) { + } catch (final Exception e) { Assert.fail("Failed to awaitGraphIndexStatus on multiple statuses."); } } + + @Test + public void testAndForceIndex() throws Exception { + JanusGraph customGraph = null; + try { + customGraph = this.getForceIndexGraph(); + final JanusGraphManagement management = customGraph.openManagement(); + final PropertyKey nameProperty = management.makePropertyKey("name").dataType(String.class).cardinality(Cardinality.SINGLE).make(); + final PropertyKey ageProperty = management.makePropertyKey("age").dataType(Integer.class).cardinality(Cardinality.SINGLE).make(); + management.buildIndex("oridx", Vertex.class).addKey(nameProperty, getStringMapping()).addKey(ageProperty).buildMixedIndex(INDEX); + management.commit(); + customGraph.tx().commit(); + final GraphTraversalSource g = customGraph.traversal(); + g.addV().property("name", "Hiro").property("age", 2).next(); + g.addV().property("name", "Totoro").property("age", 1).next(); + customGraph.tx().commit(); + assertCount(1, g.V().has("name", "Totoro")); + assertCount(1, g.V().has("age", 2)); + assertCount(1, g.V().and(__.has("name", "Hiro"),__.has("age", 2))); + assertCount(0, g.V().and(__.has("name", "Totoro"),__.has("age", 2))); + } finally { + if (customGraph != null) { + JanusGraphFactory.close(customGraph); + } + } + } + + @Test + public void testOrForceIndexUniqueMixedIndex() throws Exception { + JanusGraph customGraph = null; + try { + customGraph = this.getForceIndexGraph(); + final JanusGraphManagement management = customGraph.openManagement(); + final PropertyKey nameProperty = management.makePropertyKey("name").dataType(String.class).cardinality(Cardinality.SINGLE).make(); + final PropertyKey ageProperty = management.makePropertyKey("age").dataType(Integer.class).cardinality(Cardinality.SINGLE).make(); + final PropertyKey lengthProperty = management.makePropertyKey("length").dataType(Integer.class).cardinality(Cardinality.SINGLE).make(); + management.buildIndex("oridx", Vertex.class).addKey(nameProperty, getStringMapping()).addKey(ageProperty).addKey(lengthProperty).buildMixedIndex(INDEX); + management.commit(); + customGraph.tx().commit(); + testOr(customGraph); + } finally { + if (customGraph != null) { + JanusGraphFactory.close(customGraph); + } + } + } + + @Test + public void testOrForceIndexMixedAndCompositeIndex() throws Exception { + JanusGraph customGraph = null; + try { + customGraph = this.getForceIndexGraph(); + final JanusGraphManagement management = customGraph.openManagement(); + final PropertyKey nameProperty = management.makePropertyKey("name").dataType(String.class).cardinality(Cardinality.SINGLE).make(); + final PropertyKey ageProperty = management.makePropertyKey("age").dataType(Integer.class).cardinality(Cardinality.SINGLE).make(); + final PropertyKey lengthProperty = management.makePropertyKey("length").dataType(Integer.class).cardinality(Cardinality.SINGLE).make(); + management.buildIndex("nameidx", Vertex.class).addKey(nameProperty, getStringMapping()).buildMixedIndex(INDEX); + management.buildIndex("ageridx", Vertex.class).addKey(ageProperty).buildCompositeIndex(); + management.buildIndex("lengthidx", Vertex.class).addKey(lengthProperty).buildMixedIndex(INDEX); + management.commit(); + customGraph.tx().commit(); + testOr(customGraph); + } finally { + if (customGraph != null) { + JanusGraphFactory.close(customGraph); + } + } + } + + @Test + public void testOrPartialIndex() { + final PropertyKey nameProperty = mgmt.makePropertyKey("name").dataType(String.class).cardinality(Cardinality.SINGLE).make(); + mgmt.makePropertyKey("age").dataType(Integer.class).cardinality(Cardinality.SINGLE).make(); + final PropertyKey lengthProperty = mgmt.makePropertyKey("length").dataType(Integer.class).cardinality(Cardinality.SINGLE).make(); + mgmt.buildIndex("otheridx", Vertex.class).addKey(nameProperty, getStringMapping()).addKey(lengthProperty).buildMixedIndex(INDEX); + finishSchema(); + clopen(); + testOr(graph); + } + + private void testOr(final Graph aGraph) { + final GraphTraversalSource g = aGraph.traversal(); + final Vertex hiro = g.addV().property("name", "Hiro").property("age", 2).property("length", 90).next(); + final Vertex totoro = g.addV().property("name", "Totoro").property("age", 1).next(); + final Vertex john = g.addV().property("name", "John").property("age", 3).property("length", 110).next(); + final Vertex mike = g.addV().property("name", "Mike").property("age", 4).property("length", 130).next(); + aGraph.tx().commit(); + + assertCount(1, g.V().has("name", "Totoro")); + assertCount(1, g.V().has("age", 2)); + assertCount(1, g.V().or(__.has("name", "Hiro"),__.has("age", 2))); + assertCount(2, g.V().or(__.has("name", "Totoro"),__.has("age", 2))); + assertCount(2, g.V().or(__.has("name", "Totoro").has("age", 1),__.has("age", 2))); + assertCount(2, g.V().or(__.and(__.has("name", "Totoro"), __.has("age", 1)),__.has("age", 2))); + + assertTraversal(g.V().has("length", P.lte(100)).or(__.has("name", "Totoro"),__.has("age", P.gte(2))), hiro); + assertTraversal(g.V().or(__.has("name", "Totoro"),__.has("age", P.gte(2))).has("length", P.lte(100)), hiro); + + assertTraversal(g.V().or(__.has("name", "Totoro"),__.has("age", 2)).order().by(ORDER_AGE_DECR), hiro, totoro); + assertTraversal(g.V().or(__.has("name", "Totoro"),__.has("age", 2)).order().by(ORDER_AGE_INCR), totoro, hiro); + assertTraversal(g.V().or(__.has("name", "Hiro"),__.has("age", 2)).order().by(ORDER_AGE_INCR), hiro); + + assertTraversal(g.V().or(__.has("name", "Totoro"), __.has("length", P.lte(120)).order().by(ORDER_LENGTH_DECR)), totoro, john, hiro); + assertTraversal(g.V().or(__.has("name", "Totoro"), __.has("length", P.lte(120)).order().by(ORDER_LENGTH_INCR)), totoro, hiro, john); + assertTraversal(g.V().or(__.has("name", "John"), __.has("length", P.lte(120)).order().by(ORDER_LENGTH_INCR)), john, hiro); + + assertTraversal(g.V().or(__.has("name", "Totoro"), __.has("length", P.lte(120)).order().by(ORDER_AGE_DECR)), totoro, john, hiro); + assertTraversal(g.V().or(__.has("name", "Totoro"), __.has("length", P.lte(120)).order().by(ORDER_AGE_INCR)), totoro, hiro, john); + + assertTraversal(g.V().or(__.has("name", "Totoro"), __.has("length", P.lte(120)).order().by(ORDER_LENGTH_DECR)).order().by(ORDER_AGE_INCR), totoro, hiro, john); + assertTraversal(g.V().or(__.has("name", "Totoro"), __.has("length", P.lte(120)).order().by(ORDER_LENGTH_INCR)).order().by(ORDER_AGE_DECR), john, hiro, totoro); + + assertTraversal(g.V().or(__.has("name", "Totoro"), __.has("length", P.lte(120))).order().by(ORDER_AGE_INCR).limit(2), totoro, hiro); + assertTraversal(g.V().or(__.has("name", "Totoro"), __.has("length", P.lte(120))).order().by(ORDER_AGE_INCR).range(2, 3), john); + + assertTraversal(g.V().or(__.has("name", "Totoro"), __.has("length", P.lte(130)).order().by(ORDER_LENGTH_INCR).limit(1)), totoro, hiro); + assertTraversal(g.V().or(__.has("name", "Hiro"), __.has("length", P.lte(130)).order().by(ORDER_LENGTH_INCR).limit(1)), hiro); + assertTraversal(g.V().or(__.has("name", "Totoro"), __.has("length", P.lte(130)).order().by(ORDER_LENGTH_INCR).range(1, 2)), totoro, john); + assertTraversal(g.V().or(__.has("name", "Totoro"), __.has("length", P.lte(130)).order().by(ORDER_LENGTH_INCR).range(1, 3)).limit(2), totoro, john); + + assertTraversal(g.V().has("length", P.gte(130).or(P.lt(100))).order().by(ORDER_AGE_INCR), hiro, mike); + assertTraversal(g.V().has("length", P.gte(80).and(P.gte(130).or(P.lt(100)))).order().by(ORDER_AGE_INCR), hiro, mike); + if (indexFeatures.supportNotQueryNormalForm()) { + assertTraversal(g.V().has("length", P.gte(80).and(P.gte(130)).or(P.gte(80).and(P.lt(100)))).order().by(ORDER_AGE_INCR), hiro, mike); + } + + } + + @Test + public void testOrForceIndexPartialIndex() throws Exception { + JanusGraph customGraph = null; + try { + customGraph = this.getForceIndexGraph(); + final JanusGraphManagement management = customGraph.openManagement(); + final PropertyKey stringProperty = management.makePropertyKey("name").dataType(String.class).cardinality(Cardinality.SINGLE).make(); + management.makePropertyKey("age").dataType(Integer.class).cardinality(Cardinality.SINGLE).make(); + management.buildIndex("oridx", Vertex.class).addKey(stringProperty, getStringMapping()).buildMixedIndex(INDEX); + management.commit(); + customGraph.tx().commit(); + final GraphTraversalSource g = customGraph.traversal(); + g.addV().property("name", "Hiro").property("age", 2).next(); + g.addV().property("name", "Totoro").property("age", 1).next(); + customGraph.tx().commit(); + g.V().or(__.has("name", "Totoro"),__.has("age", 2)).hasNext(); + fail("should fail"); + } catch (final JanusGraphException e){ + assertTrue(e.getMessage().contains("Could not find a suitable index to answer graph query and graph scans are disabled")); + } finally { + if (customGraph != null) { + JanusGraphFactory.close(customGraph); + } + } + } + + @Test + public void testOrForceIndexComposite() throws Exception { + JanusGraph customGraph = null; + try { + customGraph = this.getForceIndexGraph(); + final JanusGraphManagement management = customGraph.openManagement(); + management.makePropertyKey("name").dataType(String.class).cardinality(Cardinality.SINGLE).make(); + final PropertyKey ageProperty = management.makePropertyKey("age").dataType(Integer.class).cardinality(Cardinality.SINGLE).make(); + management.buildIndex("ageridx", Vertex.class).addKey(ageProperty).buildCompositeIndex(); + management.commit(); + customGraph.tx().commit(); + final GraphTraversalSource g = customGraph.traversal(); + g.addV().property("name", "Hiro").property("age", 2).next(); + g.addV().property("name", "Totoro").property("age", 1).next(); + customGraph.tx().commit(); + g.V().has("age", P.gte(4).or(P.lt(2))).hasNext(); + fail("should fail"); + } catch (final JanusGraphException e){ + assertTrue(e.getMessage().contains("Could not find a suitable index to answer graph query and graph scans are disabled")); + } finally { + if (customGraph != null) { + JanusGraphFactory.close(customGraph); + } + } + } } diff --git a/janusgraph-test/src/main/java/org/janusgraph/testutil/JanusGraphAssert.java b/janusgraph-test/src/main/java/org/janusgraph/testutil/JanusGraphAssert.java index 653fef0074a..1e35da3041b 100644 --- a/janusgraph-test/src/main/java/org/janusgraph/testutil/JanusGraphAssert.java +++ b/janusgraph-test/src/main/java/org/janusgraph/testutil/JanusGraphAssert.java @@ -18,8 +18,12 @@ import com.google.common.collect.Iterables; import com.google.common.collect.Iterators; import org.apache.tinkerpop.gremlin.process.traversal.Traversal; +import org.apache.tinkerpop.gremlin.process.traversal.dsl.graph.GraphTraversal; import org.apache.tinkerpop.gremlin.structure.Element; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; + import java.lang.reflect.Array; import java.util.Collection; import java.util.Iterator; @@ -58,6 +62,13 @@ public static void assertNotEmpty(Object object) { org.junit.Assert.assertFalse(isEmpty(object)); } + public static void assertTraversal(GraphTraversal req, E... expectedElements) { + for (final E expectedElement : expectedElements) { + assertEquals(expectedElement, req.next()); + } + assertFalse(req.hasNext()); + } + private static boolean isEmpty(Object obj) { Preconditions.checkArgument(obj != null); if (obj instanceof Traversal) return !((Traversal) obj).hasNext(); diff --git a/janusgraph-test/src/test/java/org/janusgraph/blueprints/process/traversal/strategy/optimization/JanusGraphStepStrategyTest.java b/janusgraph-test/src/test/java/org/janusgraph/blueprints/process/traversal/strategy/optimization/JanusGraphStepStrategyTest.java index 1899d35a11a..e0c2ab3b83f 100644 --- a/janusgraph-test/src/test/java/org/janusgraph/blueprints/process/traversal/strategy/optimization/JanusGraphStepStrategyTest.java +++ b/janusgraph-test/src/test/java/org/janusgraph/blueprints/process/traversal/strategy/optimization/JanusGraphStepStrategyTest.java @@ -19,22 +19,29 @@ import org.apache.tinkerpop.gremlin.process.traversal.TraversalStrategies; import org.apache.tinkerpop.gremlin.process.traversal.TraversalStrategy; import org.apache.tinkerpop.gremlin.process.traversal.dsl.graph.DefaultGraphTraversal; -import org.apache.tinkerpop.gremlin.process.traversal.dsl.graph.GraphTraversalSource; +import org.apache.tinkerpop.gremlin.process.traversal.dsl.graph.__; +import org.apache.tinkerpop.gremlin.process.traversal.step.filter.OrStep; +import org.apache.tinkerpop.gremlin.process.traversal.step.filter.RangeGlobalStep; import org.apache.tinkerpop.gremlin.process.traversal.step.map.GraphStep; +import org.apache.tinkerpop.gremlin.process.traversal.step.util.ElementValueComparator; import org.apache.tinkerpop.gremlin.process.traversal.step.util.HasContainer; import org.apache.tinkerpop.gremlin.process.traversal.strategy.optimization.FilterRankingStrategy; import org.apache.tinkerpop.gremlin.process.traversal.strategy.optimization.InlineFilterStrategy; +import org.apache.tinkerpop.gremlin.process.traversal.util.AndP; +import org.apache.tinkerpop.gremlin.process.traversal.util.ConnectiveP; import org.apache.tinkerpop.gremlin.process.traversal.util.DefaultTraversalStrategies; +import org.apache.tinkerpop.gremlin.process.traversal.util.OrP; import org.apache.tinkerpop.gremlin.structure.T; import org.apache.tinkerpop.gremlin.structure.Vertex; - import org.apache.tinkerpop.gremlin.process.traversal.P; import org.apache.tinkerpop.gremlin.process.traversal.dsl.graph.GraphTraversal; - +import org.apache.tinkerpop.gremlin.process.traversal.dsl.graph.GraphTraversalSource; import org.janusgraph.StorageSetup; import org.janusgraph.core.JanusGraph; import org.janusgraph.core.schema.JanusGraphManagement; import org.janusgraph.graphdb.database.StandardJanusGraph; +import org.janusgraph.graphdb.predicate.ConnectiveJanusPredicate; +import org.janusgraph.graphdb.query.JanusGraphPredicate.Converter; import org.janusgraph.graphdb.tinkerpop.optimize.HasStepFolder; import org.janusgraph.graphdb.tinkerpop.optimize.JanusGraphStep; import org.janusgraph.graphdb.tinkerpop.optimize.JanusGraphStepStrategy; @@ -81,22 +88,40 @@ public void doTest() { assertEquals(this.optimized, this.original); } + @SuppressWarnings({ "unchecked", "rawtypes" }) private static GraphTraversal.Admin g_V(final Object... hasKeyValues) { final GraphTraversal.Admin traversal = new DefaultGraphTraversal<>(); final JanusGraphStep graphStep = new JanusGraphStep<>(new GraphStep<>(traversal, Vertex.class, true)); - for (int i = 0; i < hasKeyValues.length; i = i + 2) { - if (hasKeyValues[i].equals(T.id)) { + for (int i = 0; i < hasKeyValues.length; i++) { + if(hasKeyValues[i].equals(T.id)) { graphStep.addIds(Arrays.asList(hasKeyValues[i + 1])); + i++; } else if (hasKeyValues[i] instanceof HasStepFolder.OrderEntry) { final HasStepFolder.OrderEntry orderEntry = (HasStepFolder.OrderEntry) hasKeyValues[i]; graphStep.orderBy(orderEntry.key, orderEntry.order); + } else if (hasKeyValues[i] instanceof DefaultGraphTraversal && ((DefaultGraphTraversal) hasKeyValues[i]).getStartStep() instanceof OrStep){ + for (final Traversal.Admin child : ((OrStep) ((DefaultGraphTraversal) hasKeyValues[i]).getStartStep()).getLocalChildren()) { + final JanusGraphStep localGraphStep = ((JanusGraphStep) ((DefaultGraphTraversal) child).getStartStep()); + graphStep.addLocalAll(localGraphStep.getHasContainers()); + localGraphStep.getOrders().forEach(orderEntry -> graphStep.localOrderBy(localGraphStep.getHasContainers(), orderEntry.key, orderEntry.order)); + graphStep.setLocalLimit(localGraphStep.getHasContainers(), localGraphStep.getLowLimit(), localGraphStep.getHighLimit()); + } + } else if (hasKeyValues[i] instanceof DefaultGraphTraversal && ((DefaultGraphTraversal) hasKeyValues[i]).getStartStep() instanceof RangeGlobalStep){ + final RangeGlobalStep range = (RangeGlobalStep) ((DefaultGraphTraversal) hasKeyValues[i]).getStartStep(); + graphStep.setLimit((int) range.getLowRange(), (int) range.getHighRange()); + } else if (i < hasKeyValues.length -1 && hasKeyValues[i + 1] instanceof ConnectiveP) { + final ConnectiveJanusPredicate connectivePredicate = Converter.instanceConnectiveJanusPredicate((ConnectiveP) hasKeyValues[i + 1] ); + graphStep.addHasContainer(new HasContainer((String) hasKeyValues[i], new P<>(connectivePredicate, Converter.convert(((ConnectiveP) hasKeyValues[i + 1]), connectivePredicate)))); + i++; } else { graphStep.addHasContainer(new HasContainer((String) hasKeyValues[i], (P) hasKeyValues[i + 1])); + i++; } } return traversal.addStep(graphStep); } + @Parameterized.Parameters(name = "{0}") public static Iterable generateTestParameters() { final StandardJanusGraph graph = (StandardJanusGraph) StorageSetup.getInMemoryGraph(); @@ -112,7 +137,11 @@ public static Iterable generateTestParameters() { {g.V().out(), g_V().out(), Collections.emptyList()}, {g.V().has("name", "marko").out(), g_V("name", eq("marko")).out(), Collections.emptyList()}, {g.V().has("name", "marko").has("age", gt(31).and(lt(10))).out(), - g_V("name", eq("marko"), "age", gt(31), "age", lt(10)).out(), Collections.emptyList()}, + g_V("name", eq("marko"), "age", new AndP(Arrays.asList(gt(31), lt(10)))).out(), Collections.emptyList()}, + {g.V().has("name", "marko").has("age", gt(31).or(lt(10))).out(), + g_V("name", eq("marko"), "age", new OrP(Arrays.asList(gt(31), lt(10)))).out(), Collections.emptyList()}, + {g.V().has("name", "marko").has("age", gt(31).and(lt(10).or(gt(40)))).out(), + g_V("name", eq("marko"), "age", new OrP(Arrays.asList(gt(31), new AndP(Arrays.asList(lt(10), gt(40)))))).out(), Collections.emptyList()}, {g.V().has("name", "marko").or(has("age"), has("age", gt(32))).has("lang", "java"), g_V("name", eq("marko"), "lang", eq("java")).or(has("age"), has("age", gt(32))), Collections.singletonList(FilterRankingStrategy.instance())}, {g.V().has("name", "marko").as("a").or(has("age"), has("age", gt(32))).has("lang", "java"), @@ -130,7 +159,7 @@ public static Iterable generateTestParameters() { {g.V().has("name", "marko").or(not(has("age")), has("age", gt(32))).has("name", "bob").has("lang", "java"), g_V("name", eq("marko"), "name", eq("bob"), "lang", eq("java")).or(not(filter(properties("age"))), has("age", gt(32))), TraversalStrategies.GlobalCache.getStrategies(JanusGraph.class).toList()}, {g.V().has("name", eq("marko").and(eq("bob").and(eq("stephen")))).out("knows"), - g_V("name", eq("marko"), "name", eq("bob"), "name", eq("stephen")).out("knows"), Collections.emptyList()}, + g_V("name", new AndP(Arrays.asList(eq("marko"), eq("bob"),eq("stephen")))).out("knows"), Collections.emptyList()}, {g.V().hasId(1), g_V(T.id, 1), Collections.emptyList()}, {g.V().hasId(within(1, 2)), g_V(T.id, 1, T.id, 2), Collections.emptyList()}, {g.V().hasId(1).has("name", "marko"), g_V(T.id, 1, "name", eq("marko")), Collections.emptyList()}, @@ -140,6 +169,8 @@ public static Iterable generateTestParameters() { // same as above, different order {g.V().hasLabel("Person").has("lang", "java").order().by("name", Order.decr), g_V("~label", eq("Person"), "lang", eq("java"), new HasStepFolder.OrderEntry("name", Order.decr)), Collections.emptyList()}, + {g.V().hasLabel("Person").has("lang", "java").order().by(new ElementValueComparator("name", org.apache.tinkerpop.gremlin.process.traversal.Order.incr)), + g_V("~label", eq("Person"), "lang", eq("java"), new HasStepFolder.OrderEntry("name", Order.incr)), Collections.emptyList()}, // if multiple order steps are specified in a row, only the last will be folded in because it overrides previous ordering {g.V().hasLabel("Person").has("lang", "java").order().by("lang", Order.incr).order().by("name", Order.decr), g_V("~label", eq("Person"), "lang", eq("java"), new HasStepFolder.OrderEntry("name", Order.decr)), Collections.emptyList()}, @@ -152,6 +183,17 @@ public static Iterable generateTestParameters() { // Per the TinkerGraph reference implementation, multiple hasIds in a row should not be folded // into a single within(ids) lookup {g.V().hasId(1).hasId(2), g_V(T.id, 1).hasId(2), Collections.emptyList()}, + {g.V().has("name", "marko").range(10, 20), g_V("name", eq("marko"), __.range(10, 20)), Collections.emptyList()}, + {g.V().has("name", "marko").or(has("length", lt(160)), has("age", gt(32))).has("lang", "java"), + g_V("name", eq("marko"), "lang", eq("java"),__.or(g_V("length", lt(160)), g_V("age", gt(32)))), Collections.emptyList()}, + {g.V().or(has("length", lt(160)), has("age", gt(32)).range(1, 5)), + g_V(__.or(g_V("length", lt(160)), g_V("age", gt(32), __.range(1, 5)))), Collections.emptyList()}, + {g.V().or(has("length", lt(160)), has("age", gt(32)).range(1, 5)).range(10, 20), + g_V(__.or(g_V("length", lt(160)), g_V("age", gt(32), __.range(1, 5))), __.range(10, 20)), Collections.emptyList()}, + {g.V().or(__.has("name", "marko"), has("lang", "java").order().by("name", Order.decr)), + g_V(__.or(g_V("name", eq("marko")), g_V("lang", eq("java"), new HasStepFolder.OrderEntry("name", Order.decr)))), Collections.emptyList()}, + {g.V().or(__.has("name", "marko"), has("lang", "java").order().by("name", Order.decr)).order().by("lang", Order.incr), + g_V(__.or(g_V("name", eq("marko")), g_V("lang", eq("java"), new HasStepFolder.OrderEntry("name", Order.decr))), new HasStepFolder.OrderEntry("lang", Order.incr)), Collections.emptyList()}, }); } } diff --git a/janusgraph-test/src/test/java/org/janusgraph/graphdb/predicate/AndJanusPredicateTest.java b/janusgraph-test/src/test/java/org/janusgraph/graphdb/predicate/AndJanusPredicateTest.java new file mode 100644 index 00000000000..d82de2b0879 --- /dev/null +++ b/janusgraph-test/src/test/java/org/janusgraph/graphdb/predicate/AndJanusPredicateTest.java @@ -0,0 +1,52 @@ +/* + * 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.janusgraph.graphdb.predicate; + +import java.util.Arrays; +import java.util.List; + +import org.janusgraph.core.attribute.Cmp; +import org.janusgraph.core.attribute.Text; +import org.janusgraph.graphdb.query.JanusGraphPredicate; +import org.junit.Assert; +import org.junit.Test; + +/** + * @author David Clement (david.clement90@laposte.net) + */ +public class AndJanusPredicateTest extends ConnectiveJanusPredicateTest{ + + @Override + ConnectiveJanusPredicate getPredicate(List childPredicates) { + return new AndJanusPredicate(childPredicates); + } + + @Override + ConnectiveJanusPredicate getNegatePredicate(List childPredicates) { + return new OrJanusPredicate(childPredicates); + } + + @Test + public void testIsQNF() { + Assert.assertTrue(getPredicate(Arrays.asList(Text.PREFIX, Cmp.EQUAL)).isQNF()); + Assert.assertTrue(getPredicate(Arrays.asList(Text.PREFIX, Cmp.EQUAL, new OrJanusPredicate(Arrays.asList(Text.PREFIX, Cmp.EQUAL)))).isQNF()); + Assert.assertTrue(getPredicate(Arrays.asList(Text.PREFIX, Cmp.EQUAL, new AndJanusPredicate(Arrays.asList(Text.PREFIX, Cmp.EQUAL)))).isQNF()); + } +} diff --git a/janusgraph-test/src/test/java/org/janusgraph/graphdb/predicate/ConnectiveJanusGraphPTest.java b/janusgraph-test/src/test/java/org/janusgraph/graphdb/predicate/ConnectiveJanusGraphPTest.java new file mode 100644 index 00000000000..300312cd25a --- /dev/null +++ b/janusgraph-test/src/test/java/org/janusgraph/graphdb/predicate/ConnectiveJanusGraphPTest.java @@ -0,0 +1,37 @@ +package org.janusgraph.graphdb.predicate; + +import java.util.Arrays; +import java.util.List; + +import org.janusgraph.core.attribute.Cmp; +import org.janusgraph.core.attribute.Text; +import org.junit.Assert; +import org.junit.Test; + + +public class ConnectiveJanusGraphPTest { + + @Test + public void testToString() { + final AndJanusPredicate biPredicate = new AndJanusPredicate(Arrays.asList(Text.PREFIX, Cmp.EQUAL, new OrJanusPredicate(Arrays.asList(Text.PREFIX, Cmp.EQUAL)))); + final List values = Arrays.asList("john", "jo", Arrays.asList("mike","mi")); + final ConnectiveJanusGraphP predicate = new ConnectiveJanusGraphP(biPredicate, values); + Assert.assertEquals("and(textPrefix(john), =(jo), or(textPrefix(mike), =(mi)))", predicate.toString()); + } + + @Test + public void testToStringOneElement() { + final AndJanusPredicate biPredicate = new AndJanusPredicate(Arrays.asList(Text.PREFIX)); + final List values = Arrays.asList("john"); + final ConnectiveJanusGraphP predicate = new ConnectiveJanusGraphP(biPredicate, values); + Assert.assertEquals("textPrefix(john)", predicate.toString()); + } + + @Test + public void testToStringEmpty() { + final AndJanusPredicate biPredicate = new AndJanusPredicate(); + final List values = Arrays.asList(); + final ConnectiveJanusGraphP predicate = new ConnectiveJanusGraphP(biPredicate, values); + Assert.assertEquals("and()", predicate.toString()); + } +} diff --git a/janusgraph-test/src/test/java/org/janusgraph/graphdb/predicate/ConnectiveJanusPredicateTest.java b/janusgraph-test/src/test/java/org/janusgraph/graphdb/predicate/ConnectiveJanusPredicateTest.java new file mode 100644 index 00000000000..eec1e9a7e9e --- /dev/null +++ b/janusgraph-test/src/test/java/org/janusgraph/graphdb/predicate/ConnectiveJanusPredicateTest.java @@ -0,0 +1,113 @@ +/* + * 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.janusgraph.graphdb.predicate; + +import java.util.Arrays; +import java.util.List; + +import org.janusgraph.core.attribute.Cmp; +import org.janusgraph.core.attribute.Geo; +import org.janusgraph.core.attribute.Geoshape; +import org.janusgraph.core.attribute.Text; +import org.janusgraph.graphdb.query.JanusGraphPredicate; +import org.junit.Assert; +import org.junit.Test; + +/** + * @author David Clement (david.clement90@laposte.net) + */ +public abstract class ConnectiveJanusPredicateTest { + + + abstract ConnectiveJanusPredicate getPredicate(List childPredicates); + + abstract ConnectiveJanusPredicate getNegatePredicate(List childPredicates); + + @Test + public void testIsValidConditionNotAList() { + Assert.assertFalse(getPredicate(Arrays.asList()).isValidCondition(3)); + } + + @Test + public void testIsValidConditionDifferentSize() { + Assert.assertFalse(getPredicate(Arrays.asList()).isValidCondition(Arrays.asList(3))); + } + + @Test + public void testIsValidConditionOk() { + Assert.assertTrue(getPredicate(Arrays.asList(Text.CONTAINS, Cmp.EQUAL, Geo.WITHIN)).isValidCondition(Arrays.asList("john", 3, Geoshape.point(2.0, 4.0)))); + } + + @Test + public void testIsValidConditionKoFirst() { + Assert.assertFalse(getPredicate(Arrays.asList(Text.CONTAINS, Cmp.EQUAL, Geo.WITHIN)).isValidCondition(Arrays.asList(1L, 3, Geoshape.point(2.0, 4.0)))); + } + + @Test + public void testIsValidConditionKo() { + Assert.assertFalse(getPredicate(Arrays.asList(Text.CONTAINS, Cmp.EQUAL, Geo.WITHIN)).isValidCondition(Arrays.asList("john", 3, 1L))); + } + + @Test + public void testIsValidTypeOk() { + Assert.assertTrue(getPredicate(Arrays.asList(Text.CONTAINS, Cmp.EQUAL)).isValidValueType(String.class)); + } + + @Test + public void testIsValidKo() { + Assert.assertFalse(getPredicate(Arrays.asList(Text.CONTAINS, Cmp.EQUAL)).isValidValueType(Integer.class)); + } + + @Test + public void testHasNegationOk() { + Assert.assertTrue(getPredicate(Arrays.asList(Geo.INTERSECT, Cmp.EQUAL)).hasNegation()); + } + + @Test + public void testHasNegationKo() { + Assert.assertFalse(getPredicate(Arrays.asList(Text.CONTAINS, Cmp.EQUAL)).hasNegation()); + } + + @Test + public void testNegate() { + Assert.assertEquals(getNegatePredicate(Arrays.asList(Geo.DISJOINT, Cmp.NOT_EQUAL)), getPredicate(Arrays.asList(Geo.INTERSECT, Cmp.EQUAL)).negate()); + } + + @Test + public void testTestNotAList() { + Assert.assertFalse(getPredicate(Arrays.asList()).test("john","jo")); + } + + @Test + public void testTestDifferentSize() { + Assert.assertFalse(getPredicate(Arrays.asList()).test("john",Arrays.asList("jo"))); + } + + + @Test + public void testTest() { + final ConnectiveJanusPredicate predicate = getPredicate(Arrays.asList(Text.PREFIX, Cmp.EQUAL)); + Assert.assertTrue(predicate.test("john",Arrays.asList("jo", "john"))); + Assert.assertEquals(predicate.isOr(), predicate.test("john",Arrays.asList("jo", "mike"))); + Assert.assertEquals(predicate.isOr(), predicate.test("john",Arrays.asList("mi", "john"))); + Assert.assertFalse(predicate.test("john",Arrays.asList("mi", "mike"))); + } + +} diff --git a/janusgraph-test/src/test/java/org/janusgraph/graphdb/predicate/OrJanusPredicateTest.java b/janusgraph-test/src/test/java/org/janusgraph/graphdb/predicate/OrJanusPredicateTest.java new file mode 100644 index 00000000000..0f6c06886ba --- /dev/null +++ b/janusgraph-test/src/test/java/org/janusgraph/graphdb/predicate/OrJanusPredicateTest.java @@ -0,0 +1,52 @@ +/* + * 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.janusgraph.graphdb.predicate; + +import java.util.Arrays; +import java.util.List; + +import org.janusgraph.core.attribute.Cmp; +import org.janusgraph.core.attribute.Text; +import org.janusgraph.graphdb.query.JanusGraphPredicate; +import org.junit.Assert; +import org.junit.Test; + +/** + * @author David Clement (david.clement90@laposte.net) + */ +public class OrJanusPredicateTest extends ConnectiveJanusPredicateTest{ + + @Override + ConnectiveJanusPredicate getPredicate(List childPredicates) { + return new OrJanusPredicate(childPredicates); + } + + @Override + ConnectiveJanusPredicate getNegatePredicate(List childPredicates) { + return new AndJanusPredicate(childPredicates); + } + + @Test + public void testIsQNF() { + Assert.assertTrue(getPredicate(Arrays.asList(Text.PREFIX, Cmp.EQUAL)).isQNF()); + Assert.assertTrue(getPredicate(Arrays.asList(Text.PREFIX, Cmp.EQUAL, new OrJanusPredicate(Arrays.asList(Text.PREFIX, Cmp.EQUAL)))).isQNF()); + Assert.assertFalse(getPredicate(Arrays.asList(Text.PREFIX, Cmp.EQUAL, new AndJanusPredicate(Arrays.asList(Text.PREFIX, Cmp.EQUAL)))).isQNF()); + } +}