// 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; import com.google.common.collect.ImmutableList; import com.google.common.collect.Iterables; import com.google.common.collect.Iterators; import com.google.common.collect.Sets; import org.janusgraph.core.Cardinality; import org.janusgraph.core.EdgeLabel; import org.janusgraph.core.PropertyKey; import org.janusgraph.core.JanusGraphException; import org.janusgraph.core.JanusGraphFactory; import org.janusgraph.core.JanusGraph; import org.janusgraph.core.JanusGraphQuery; import org.janusgraph.core.JanusGraphIndexQuery; import org.janusgraph.core.JanusGraphTransaction; import org.janusgraph.core.JanusGraphVertex; import org.janusgraph.core.JanusGraphVertexProperty; import org.janusgraph.core.VertexLabel; 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.core.log.TransactionRecovery; import org.janusgraph.core.schema.Mapping; import org.janusgraph.core.schema.Parameter; import org.janusgraph.core.schema.SchemaAction; import org.janusgraph.core.schema.SchemaStatus; import org.janusgraph.core.schema.JanusGraphIndex; import org.janusgraph.core.util.ManagementUtil; import org.janusgraph.diskstorage.Backend; import org.janusgraph.diskstorage.BackendException; import org.janusgraph.diskstorage.configuration.WriteConfiguration; import org.janusgraph.diskstorage.indexing.IndexFeatures; import org.janusgraph.diskstorage.log.kcvs.KCVSLog; import org.janusgraph.diskstorage.util.time.TimestampProvider; import org.janusgraph.example.GraphOfTheGodsFactory; import org.janusgraph.graphdb.database.management.ManagementSystem; import org.janusgraph.graphdb.internal.ElementCategory; import org.janusgraph.graphdb.internal.Order; import org.janusgraph.graphdb.log.StandardTransactionLogProcessor; 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.structure.Direction; import org.apache.tinkerpop.gremlin.structure.Edge; import org.apache.tinkerpop.gremlin.structure.Vertex; import org.apache.tinkerpop.gremlin.structure.VertexProperty; import org.junit.Assert; import org.junit.Rule; import org.junit.Test; import org.junit.experimental.categories.Category; import org.junit.rules.TestName; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.time.Duration; import java.time.Instant; import java.time.temporal.ChronoUnit; import java.util.Arrays; import java.util.Date; import java.util.HashSet; import java.util.Set; import java.util.UUID; import java.util.concurrent.ExecutionException; import java.util.concurrent.TimeUnit; import java.util.function.Function; import static org.janusgraph.graphdb.JanusGraphTest.evaluateQuery; import static org.janusgraph.graphdb.configuration.GraphDatabaseConfiguration.*; import static org.janusgraph.testutil.JanusGraphAssert.*; import static org.apache.tinkerpop.gremlin.process.traversal.Order.decr; import static org.apache.tinkerpop.gremlin.process.traversal.Order.incr; import static org.junit.Assert.*; /** * @author Matthias Broecheler (me@matthiasb.com) */ public abstract class JanusGraphIndexTest extends JanusGraphBaseTest { public static final String INDEX = GraphOfTheGodsFactory.INDEX_NAME; public static final String VINDEX = "v" + INDEX; public static final String EINDEX = "e" + INDEX; public static final String PINDEX = "p" + INDEX; public final boolean supportsGeoPoint; public final boolean supportsNumeric; public final boolean supportsText; public IndexFeatures indexFeatures; private static final Logger log = LoggerFactory.getLogger(JanusGraphIndexTest.class); protected JanusGraphIndexTest(boolean supportsGeoPoint, boolean supportsNumeric, boolean supportsText) { this.supportsGeoPoint = supportsGeoPoint; this.supportsNumeric = supportsNumeric; this.supportsText = supportsText; } private Parameter getStringMapping() { if (indexFeatures.supportsStringMapping(Mapping.STRING)) return Mapping.STRING.asParameter(); else if (indexFeatures.supportsStringMapping(Mapping.TEXTSTRING)) return Mapping.TEXTSTRING.asParameter(); throw new AssertionError("String mapping not supported"); } private Parameter getTextMapping() { if (indexFeatures.supportsStringMapping(Mapping.TEXT)) return Mapping.TEXT.asParameter(); else if (indexFeatures.supportsStringMapping(Mapping.TEXTSTRING)) return Mapping.TEXTSTRING.asParameter(); throw new AssertionError("Text mapping not supported"); } private Parameter getFieldMap(PropertyKey key) { return ParameterType.MAPPED_NAME.getParameter(key.name()); } public abstract boolean supportsLuceneStyleQueries(); public abstract boolean supportsWildcardQuery(); @Override public void open(WriteConfiguration config) { super.open(config); indexFeatures = graph.getBackend().getIndexFeatures().get(INDEX); } @Override public void clopen(Object... settings) { graph.tx().commit(); super.clopen(settings); } @Rule public TestName methodName = new TestName(); /** * Tests the {@link org.janusgraph.example.GraphOfTheGodsFactory#load(org.janusgraph.core.JanusGraph)} * method used as the standard example that ships with JanusGraph. */ @Test public void testGraphOfTheGods() { GraphOfTheGodsFactory.load(graph); assertGraphOfTheGods(graph); } public static void assertGraphOfTheGods(JanusGraph gotg) { assertCount(12, gotg.query().vertices()); assertCount(3, gotg.query().has(LABEL_NAME, "god").vertices()); JanusGraphVertex h = getOnlyVertex(gotg.query().has("name", "hercules")); assertEquals(30, h.<Integer>value("age").intValue()); assertEquals("demigod", h.label()); assertCount(5, h.query().direction(Direction.BOTH).edges()); gotg.tx().commit(); } @Test public void testSimpleUpdate() { 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); finishSchema(); JanusGraphVertex v = tx.addVertex("name", "Marko Rodriguez"); 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")); 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")); v = getOnlyVertex(tx.query().has("name", Text.CONTAINS, "marko")); v.property(VertexProperty.Cardinality.single, "name", "Marko"); e = getOnlyEdge(v.query().direction(Direction.OUT)); e.property("name", "Tubu Rubu"); 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")); 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")); } @Test public void testIndexing() { PropertyKey text = makeKey("text", String.class); createExternalVertexIndex(text, INDEX); createExternalEdgeIndex(text, INDEX); PropertyKey location = makeKey("location", Geoshape.class); createExternalVertexIndex(location, INDEX); createExternalEdgeIndex(location, INDEX); 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); createExternalVertexIndex(time, INDEX); createExternalEdgeIndex(time, INDEX); 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); createExternalVertexIndex(group, INDEX); createExternalEdgeIndex(group, INDEX); makeVertexIndexedKey("uid", Integer.class); ((StandardEdgeLabelMaker) mgmt.makeEdgeLabel("knows")).sortKey(time).signature(location,boundary).make(); finishSchema(); clopen(); String[] words = {"world", "aurelius", "janusgraph", "graph"}; int numCategories = 5; int numGroups = 10; double distance, offset; int numV = 100; final int originalNumV = numV; for (int i = 0; i < numV; i++) { 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); v.property(VertexProperty.Cardinality.single, "text", "Vertex " + words[i % words.length]); v.property(VertexProperty.Cardinality.single, "time", i); offset = (i % 2 == 0 ? 1 : -1) * (i * 50.0 / numV); v.property(VertexProperty.Cardinality.single, "location", Geoshape.point(0.0 + offset, 0.0 + offset)); if (i % 2 == 0) { v.property(VertexProperty.Cardinality.single, "boundary", Geoshape.line(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}}))); } else { 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))); e.property("text", "Vertex " + words[i % words.length]); e.property("time", i); e.property("category", i % numCategories); e.property("group", i % numGroups); e.property("location", Geoshape.point(0.0 + offset, 0.0 + offset)); if (i % 2 == 0) { e.property("boundary", Geoshape.line(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}}))); } else { e.property("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}}))); } } for (int i = 0; i < words.length; i++) { int expectedSize = numV / words.length; assertCount(expectedSize, tx.query().has("text", Text.CONTAINS, words[i]).vertices()); assertCount(expectedSize, tx.query().has("text", Text.CONTAINS, words[i]).edges()); //Test ordering for (String orderKey : new String[]{"time", "category"}) { for (Order order : Order.values()) { for (JanusGraphQuery traversal : ImmutableList.of( tx.query().has("text", Text.CONTAINS, words[i]).orderBy(orderKey, order.getTP()), tx.query().has("text", Text.CONTAINS, words[i]).orderBy(orderKey, order.getTP()) )) { verifyElementOrder(traversal.vertices(), orderKey, order, expectedSize); } } } } assertCount(3, tx.query().has("group", 3).orderBy("time", incr).limit(3).vertices()); assertCount(3, tx.query().has("group", 3).orderBy("time", decr).limit(3).edges()); for (int i = 0; i < numV / 2; i += numV / 10) { assertCount(i, tx.query().has("time", Cmp.GREATER_THAN_EQUAL, i).has("time", Cmp.LESS_THAN, i + i).vertices()); assertCount(i, tx.query().has("time", Cmp.GREATER_THAN_EQUAL, i).has("time", Cmp.LESS_THAN, i + i).edges()); } for (int i = 0; i < numV; i += 5) { testGeo(i, originalNumV, numV, "location", "boundary"); } //Queries combining mixed and composite indexes assertCount(4, tx.query().has("category", 1).interval("time", 10, 28).vertices()); 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()); offset = (19 * 50.0 / originalNumV); 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()); assertCount(numV, tx.query().vertices()); assertCount(numV, tx.query().edges()); //-------------- clopen(); //########################## //Copied from above //########################## for (int i = 0; i < words.length; i++) { int expectedSize = numV / words.length; assertCount(expectedSize, tx.query().has("text", Text.CONTAINS, words[i]).vertices()); assertCount(expectedSize, tx.query().has("text", Text.CONTAINS, words[i]).edges()); //Test ordering for (String orderKey : new String[]{"time", "category"}) { for (Order order : Order.values()) { for (JanusGraphQuery traversal : ImmutableList.of( tx.query().has("text", Text.CONTAINS, words[i]).orderBy(orderKey, order.getTP()), tx.query().has("text", Text.CONTAINS, words[i]).orderBy(orderKey, order.getTP()) )) { verifyElementOrder(traversal.vertices(), orderKey, order, expectedSize); } } } } assertCount(3, tx.query().has("group", 3).orderBy("time", incr).limit(3).vertices()); assertCount(3, tx.query().has("group", 3).orderBy("time", decr).limit(3).edges()); for (int i = 0; i < numV / 2; i += numV / 10) { assertCount(i, tx.query().has("time", Cmp.GREATER_THAN_EQUAL, i).has("time", Cmp.LESS_THAN, i + i).vertices()); assertCount(i, tx.query().has("time", Cmp.GREATER_THAN_EQUAL, i).has("time", Cmp.LESS_THAN, i + i).edges()); } for (int i = 0; i < numV; i += 5) { testGeo(i, originalNumV, numV, "location", "boundary"); } //Queries combining mixed and composite indexes assertCount(4, tx.query().has("category", 1).interval("time", 10, 28).vertices()); 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()); offset = (19 * 50.0 / originalNumV); 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()); assertCount(numV, tx.query().vertices()); assertCount(numV, tx.query().edges()); newTx(); int numDelete = 12; for (int i = numV - numDelete; i < numV; i++) { getVertex("uid", i).remove(); } numV = numV - numDelete; //Copied from above for (int i = 0; i < words.length; i++) { assertCount(numV / words.length, tx.query().has("text", Text.CONTAINS, words[i]).vertices()); assertCount(numV / words.length, tx.query().has("text", Text.CONTAINS, words[i]).edges()); } for (int i = 0; i < numV / 2; i += numV / 10) { assertCount(i, tx.query().has("time", Cmp.GREATER_THAN_EQUAL, i).has("time", Cmp.LESS_THAN, i + i).vertices()); assertCount(i, tx.query().has("time", Cmp.GREATER_THAN_EQUAL, i).has("time", Cmp.LESS_THAN, i + i).edges()); } for (int i = 0; i < numV; i += 5) { testGeo(i, originalNumV, numV, "location", "boundary"); } assertCount(5, tx.query().has("time", Cmp.GREATER_THAN_EQUAL, 10).has("time", Cmp.LESS_THAN, 30).has("text", Text.CONTAINS, words[0]).vertices()); offset = (19 * 50.0 / originalNumV); 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()); assertCount(numV, tx.query().vertices()); assertCount(numV, tx.query().edges()); } /** * Tests indexing boolean */ @Test public void testBooleanIndexing() { PropertyKey name = makeKey("visible", Boolean.class); mgmt.buildIndex("booleanIndex", Vertex.class). addKey(name).buildMixedIndex(INDEX); finishSchema(); clopen(); JanusGraphVertex v1 = graph.addVertex(); v1.property("visible", true); JanusGraphVertex v2 = graph.addVertex(); v2.property("visible", false); assertCount(2, graph.vertices()); assertEquals(v1, getOnlyVertex(graph.query().has("visible", true))); assertEquals(v2, getOnlyVertex(graph.query().has("visible", false))); assertEquals(v2, getOnlyVertex(graph.query().has("visible", Cmp.NOT_EQUAL, true))); assertEquals(v1, getOnlyVertex(graph.query().has("visible", Cmp.NOT_EQUAL, false))); clopen();//Flush the index assertCount(2, graph.vertices()); assertEquals(v1, getOnlyVertex(graph.query().has("visible", true))); assertEquals(v2, getOnlyVertex(graph.query().has("visible", false))); assertEquals(v2, getOnlyVertex(graph.query().has("visible", Cmp.NOT_EQUAL, true))); assertEquals(v1, getOnlyVertex(graph.query().has("visible", Cmp.NOT_EQUAL, false))); } /** * Tests indexing dates */ @Test public void testDateIndexing() { PropertyKey name = makeKey("date", Date.class); mgmt.buildIndex("dateIndex", Vertex.class). addKey(name).buildMixedIndex(INDEX); finishSchema(); clopen(); JanusGraphVertex v1 = graph.addVertex(); v1.property("date", new Date(1)); JanusGraphVertex v2 = graph.addVertex(); v2.property("date", new Date(2000)); assertEquals(v1, getOnlyVertex(graph.query().has("date", Cmp.EQUAL, new Date(1)))); assertEquals(v2, getOnlyVertex(graph.query().has("date", Cmp.GREATER_THAN, new Date(1)))); assertEquals(Sets.newHashSet(v1, v2), Sets.newHashSet(graph.query().has("date", Cmp.GREATER_THAN_EQUAL, new Date(1)).vertices())); assertEquals(v1, getOnlyVertex(graph.query().has("date", Cmp.LESS_THAN, new Date(2000)))); assertEquals(Sets.newHashSet(v1, v2), Sets.newHashSet(graph.query().has("date", Cmp.LESS_THAN_EQUAL, new Date(2000)).vertices())); assertEquals(v2, getOnlyVertex(graph.query().has("date", Cmp.NOT_EQUAL, new Date(1)))); clopen();//Flush the index assertEquals(v1, getOnlyVertex(graph.query().has("date", Cmp.EQUAL, new Date(1)))); assertEquals(v2, getOnlyVertex(graph.query().has("date", Cmp.GREATER_THAN, new Date(1)))); assertEquals(Sets.newHashSet(v1, v2), Sets.newHashSet(graph.query().has("date", Cmp.GREATER_THAN_EQUAL, new Date(1)).vertices())); assertEquals(v1, getOnlyVertex(graph.query().has("date", Cmp.LESS_THAN, new Date(2000)))); assertEquals(Sets.newHashSet(v1, v2), Sets.newHashSet(graph.query().has("date", Cmp.LESS_THAN_EQUAL, new Date(2000)).vertices())); assertEquals(v2, getOnlyVertex(graph.query().has("date", Cmp.NOT_EQUAL, new Date(1)))); } /** * Tests indexing instants */ @Test public void testInstantIndexing() { 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); JanusGraphVertex v1 = graph.addVertex(); v1.property("instant", firstTimestamp); JanusGraphVertex v2 = graph.addVertex(); v2.property("instant", secondTimestamp); testInstant(firstTimestamp, secondTimestamp, v1, v2); firstTimestamp = Instant.ofEpochSecond(0, 1); v1 = (JanusGraphVertex) graph.vertices(v1.id()).next(); v1.property("instant", firstTimestamp); if (indexFeatures.supportsNanoseconds()) { testInstant(firstTimestamp, secondTimestamp, v1, v2); } else { clopen();//Flush the index try { assertEquals(v1, getOnlyVertex(graph.query().has("instant", Cmp.EQUAL, firstTimestamp))); Assert.fail("Should have failed to update the index"); } catch (Exception e) { } } } private void testInstant(Instant firstTimestamp, Instant secondTimestamp, JanusGraphVertex v1, JanusGraphVertex v2) { assertEquals(v1, getOnlyVertex(graph.query().has("instant", Cmp.EQUAL, firstTimestamp))); assertEquals(v2, getOnlyVertex(graph.query().has("instant", Cmp.GREATER_THAN, firstTimestamp))); assertEquals(Sets.newHashSet(v1, v2), Sets.newHashSet(graph.query().has("instant", Cmp.GREATER_THAN_EQUAL, firstTimestamp).vertices())); assertEquals(v1, getOnlyVertex(graph.query().has("instant", Cmp.LESS_THAN, secondTimestamp))); assertEquals(Sets.newHashSet(v1, v2), Sets.newHashSet(graph.query().has("instant", Cmp.LESS_THAN_EQUAL, secondTimestamp).vertices())); assertEquals(v2, getOnlyVertex(graph.query().has("instant", Cmp.NOT_EQUAL, firstTimestamp))); clopen();//Flush the index assertEquals(v1, getOnlyVertex(graph.query().has("instant", Cmp.EQUAL, firstTimestamp))); assertEquals(v2, getOnlyVertex(graph.query().has("instant", Cmp.GREATER_THAN, firstTimestamp))); assertEquals(Sets.newHashSet(v1, v2), Sets.newHashSet(graph.query().has("instant", Cmp.GREATER_THAN_EQUAL, firstTimestamp).vertices())); assertEquals(v1, getOnlyVertex(graph.query().has("instant", Cmp.LESS_THAN, secondTimestamp))); assertEquals(Sets.newHashSet(v1, v2), Sets.newHashSet(graph.query().has("instant", Cmp.LESS_THAN_EQUAL, secondTimestamp).vertices())); assertEquals(v2, getOnlyVertex(graph.query().has("instant", Cmp.NOT_EQUAL, firstTimestamp))); } /** * Tests indexing boolean */ @Test public void testUUIDIndexing() { 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(); JanusGraphVertex v1 = graph.addVertex(); v1.property("uid", uid1); JanusGraphVertex v2 = graph.addVertex(); v2.property("uid", uid2); assertCount(2, graph.query().vertices()); assertEquals(v1, getOnlyVertex(graph.query().has("uid", uid1))); assertEquals(v2, getOnlyVertex(graph.query().has("uid", uid2))); assertEquals(v2, getOnlyVertex(graph.query().has("uid", Cmp.NOT_EQUAL, uid1))); assertEquals(v1, getOnlyVertex(graph.query().has("uid", Cmp.NOT_EQUAL, uid2))); clopen();//Flush the index assertCount(2, graph.query().vertices()); assertEquals(v1, getOnlyVertex(graph.query().has("uid", uid1))); assertEquals(v2, getOnlyVertex(graph.query().has("uid", uid2))); assertEquals(v2, getOnlyVertex(graph.query().has("uid", Cmp.NOT_EQUAL, uid1))); assertEquals(v1, getOnlyVertex(graph.query().has("uid", Cmp.NOT_EQUAL, uid2))); } /** * Tests conditional indexing and the different management features */ @Test public void testConditionalIndexing() { PropertyKey name = makeKey("name", String.class); PropertyKey weight = makeKey("weight", Double.class); PropertyKey text = makeKey("text", String.class); VertexLabel person = mgmt.makeVertexLabel("person").make(); VertexLabel org = mgmt.makeVertexLabel("org").make(); JanusGraphIndex index1 = mgmt.buildIndex("index1", Vertex.class). addKey(name, getStringMapping()).buildMixedIndex(INDEX); JanusGraphIndex index2 = mgmt.buildIndex("index2", Vertex.class).indexOnly(person). addKey(text, getTextMapping()).addKey(weight).buildMixedIndex(INDEX); JanusGraphIndex index3 = mgmt.buildIndex("index3", Vertex.class).indexOnly(org). addKey(text, getTextMapping()).addKey(weight).buildMixedIndex(INDEX); // ########### INSPECTION & FAILURE ############## assertTrue(mgmt.containsGraphIndex("index1")); assertFalse(mgmt.containsGraphIndex("index")); assertCount(3, mgmt.getGraphIndexes(Vertex.class)); assertNull(mgmt.getGraphIndex("indexx")); name = mgmt.getPropertyKey("name"); weight = mgmt.getPropertyKey("weight"); text = mgmt.getPropertyKey("text"); person = mgmt.getVertexLabel("person"); org = mgmt.getVertexLabel("org"); index1 = mgmt.getGraphIndex("index1"); index2 = mgmt.getGraphIndex("index2"); index3 = mgmt.getGraphIndex("index3"); assertTrue(Vertex.class.isAssignableFrom(index1.getIndexedElement())); assertEquals("index2", index2.name()); assertEquals(INDEX, index3.getBackingIndex()); assertFalse(index2.isUnique()); assertEquals(2, index3.getFieldKeys().length); assertEquals(1, index1.getFieldKeys().length); assertEquals(3, index3.getParametersFor(text).length); assertEquals(2, index3.getParametersFor(weight).length); try { //Already exists mgmt.buildIndex("index2", Vertex.class).addKey(weight).buildMixedIndex(INDEX); fail(); } catch (IllegalArgumentException e) { } try { //Already exists mgmt.buildIndex("index2", Vertex.class).addKey(weight).buildCompositeIndex(); fail(); } catch (IllegalArgumentException e) { } try { //Key is already added mgmt.addIndexKey(index2, weight); fail(); } catch (IllegalArgumentException e) { } finishSchema(); clopen(); // ########### INSPECTION & FAILURE (copied from above) ############## assertTrue(mgmt.containsGraphIndex("index1")); assertFalse(mgmt.containsGraphIndex("index")); assertCount(3, mgmt.getGraphIndexes(Vertex.class)); assertNull(mgmt.getGraphIndex("indexx")); name = mgmt.getPropertyKey("name"); weight = mgmt.getPropertyKey("weight"); text = mgmt.getPropertyKey("text"); person = mgmt.getVertexLabel("person"); org = mgmt.getVertexLabel("org"); index1 = mgmt.getGraphIndex("index1"); index2 = mgmt.getGraphIndex("index2"); index3 = mgmt.getGraphIndex("index3"); assertTrue(Vertex.class.isAssignableFrom(index1.getIndexedElement())); assertEquals("index2", index2.name()); assertEquals(INDEX, index3.getBackingIndex()); assertFalse(index2.isUnique()); assertEquals(2, index3.getFieldKeys().length); assertEquals(1, index1.getFieldKeys().length); assertEquals(3, index3.getParametersFor(text).length); assertEquals(2, index3.getParametersFor(weight).length); try { //Already exists mgmt.buildIndex("index2", Vertex.class).addKey(weight).buildMixedIndex(INDEX); fail(); } catch (IllegalArgumentException e) { } try { //Already exists mgmt.buildIndex("index2", Vertex.class).addKey(weight).buildCompositeIndex(); fail(); } catch (IllegalArgumentException e) { } try { //Key is already added mgmt.addIndexKey(index2, weight); fail(); } catch (IllegalArgumentException e) { } // ########### TRANSACTIONAL ############## weight = tx.getPropertyKey("weight"); final int numV = 200; String[] strs = {"houseboat", "humanoid", "differential", "extraordinary"}; String[] strs2 = new String[strs.length]; for (int i = 0; i < strs.length; i++) strs2[i] = strs[i] + " " + strs[i]; final int modulo = 5; assert numV % (modulo * strs.length * 2) == 0; for (int i = 0; i < numV; i++) { JanusGraphVertex v = tx.addVertex(i % 2 == 0 ? "person" : "org"); v.property("name", strs[i % strs.length]); v.property("text", strs[i % strs.length]); v.property("weight", (i % modulo) + 0.5); } //########## QUERIES ################ evaluateQuery(tx.query().has("text", Text.CONTAINS, strs[0]).has(LABEL_NAME, Cmp.EQUAL, "person"), ElementCategory.VERTEX, numV / strs.length, new boolean[]{true, true}, index2.name()); evaluateQuery(tx.query().has("text", Text.CONTAINS, strs[0]).has(LABEL_NAME, Cmp.EQUAL, "person").orderBy("weight", decr), ElementCategory.VERTEX, numV / strs.length, new boolean[]{true, true}, weight, Order.DESC, index2.name()); evaluateQuery(tx.query().has("text", Text.CONTAINS, strs[3]).has(LABEL_NAME, Cmp.EQUAL, "org"), ElementCategory.VERTEX, numV / strs.length, new boolean[]{true, true}, index3.name()); evaluateQuery(tx.query().has("text", Text.CONTAINS, strs[1]).has(LABEL_NAME, Cmp.EQUAL, "org").orderBy("weight", decr), ElementCategory.VERTEX, numV / strs.length, new boolean[]{true, true}, weight, Order.DESC, index3.name()); evaluateQuery(tx.query().has("text", Text.CONTAINS, strs[0]).has("weight", Cmp.EQUAL, 2.5).has(LABEL_NAME, Cmp.EQUAL, "person"), ElementCategory.VERTEX, numV / (modulo * strs.length), new boolean[]{true, true}, index2.name()); evaluateQuery(tx.query().has("name", Cmp.EQUAL, strs[2]).has(LABEL_NAME, Cmp.EQUAL, "person"), ElementCategory.VERTEX, numV / strs.length, new boolean[]{false, true}, index1.name()); evaluateQuery(tx.query().has("name", Cmp.EQUAL, strs[3]).has(LABEL_NAME, Cmp.EQUAL, "person"), ElementCategory.VERTEX, 0, new boolean[]{false, true}, index1.name()); evaluateQuery(tx.query().has("name", Cmp.EQUAL, strs[0]), ElementCategory.VERTEX, numV / strs.length, new boolean[]{true, true}, index1.name()); evaluateQuery(tx.query().has("name", Cmp.EQUAL, strs[2]).has("text", Text.CONTAINS, strs[2]).has(LABEL_NAME, Cmp.EQUAL, "person"), ElementCategory.VERTEX, numV / strs.length, new boolean[]{true, true}, index1.name(), index2.name()); evaluateQuery(tx.query().has("name", Cmp.EQUAL, strs[0]).has("text", Text.CONTAINS, strs[0]).has(LABEL_NAME, Cmp.EQUAL, "person").orderBy("weight", incr), ElementCategory.VERTEX, numV / strs.length, new boolean[]{true, true}, weight, Order.ASC, index1.name(), index2.name()); evaluateQuery(tx.query().has("text", Text.CONTAINS, strs[0]), ElementCategory.VERTEX, numV / strs.length, new boolean[]{false, true}); evaluateQuery(tx.query().has("text", Text.CONTAINS, strs[0]).orderBy("weight", incr), ElementCategory.VERTEX, numV / strs.length, new boolean[]{false, false}, weight, Order.ASC); clopen(); weight = tx.getPropertyKey("weight"); //########## QUERIES (copied from above) ################ evaluateQuery(tx.query().has("text", Text.CONTAINS, strs[0]).has(LABEL_NAME, Cmp.EQUAL, "person"), ElementCategory.VERTEX, numV / strs.length, new boolean[]{true, true}, index2.name()); evaluateQuery(tx.query().has("text", Text.CONTAINS, strs[0]).has(LABEL_NAME, Cmp.EQUAL, "person").orderBy("weight", decr), ElementCategory.VERTEX, numV / strs.length, new boolean[]{true, true}, weight, Order.DESC, index2.name()); evaluateQuery(tx.query().has("text", Text.CONTAINS, strs[3]).has(LABEL_NAME, Cmp.EQUAL, "org"), ElementCategory.VERTEX, numV / strs.length, new boolean[]{true, true}, index3.name()); evaluateQuery(tx.query().has("text", Text.CONTAINS, strs[1]).has(LABEL_NAME, Cmp.EQUAL, "org").orderBy("weight", decr), ElementCategory.VERTEX, numV / strs.length, new boolean[]{true, true}, weight, Order.DESC, index3.name()); evaluateQuery(tx.query().has("text", Text.CONTAINS, strs[0]).has("weight", Cmp.EQUAL, 2.5).has(LABEL_NAME, Cmp.EQUAL, "person"), ElementCategory.VERTEX, numV / (modulo * strs.length), new boolean[]{true, true}, index2.name()); evaluateQuery(tx.query().has("name", Cmp.EQUAL, strs[2]).has(LABEL_NAME, Cmp.EQUAL, "person"), ElementCategory.VERTEX, numV / strs.length, new boolean[]{false, true}, index1.name()); evaluateQuery(tx.query().has("name", Cmp.EQUAL, strs[3]).has(LABEL_NAME, Cmp.EQUAL, "person"), ElementCategory.VERTEX, 0, new boolean[]{false, true}, index1.name()); evaluateQuery(tx.query().has("name", Cmp.EQUAL, strs[0]), ElementCategory.VERTEX, numV / strs.length, new boolean[]{true, true}, index1.name()); evaluateQuery(tx.query().has("name", Cmp.EQUAL, strs[2]).has("text", Text.CONTAINS, strs[2]).has(LABEL_NAME, Cmp.EQUAL, "person"), ElementCategory.VERTEX, numV / strs.length, new boolean[]{true, true}, index1.name(), index2.name()); evaluateQuery(tx.query().has("name", Cmp.EQUAL, strs[0]).has("text", Text.CONTAINS, strs[0]).has(LABEL_NAME, Cmp.EQUAL, "person").orderBy("weight", incr), ElementCategory.VERTEX, numV / strs.length, new boolean[]{true, true}, weight, Order.ASC, index1.name(), index2.name()); evaluateQuery(tx.query().has("text", Text.CONTAINS, strs[0]), ElementCategory.VERTEX, numV / strs.length, new boolean[]{false, true}); evaluateQuery(tx.query().has("text", Text.CONTAINS, strs[0]).orderBy("weight", incr), ElementCategory.VERTEX, numV / strs.length, new boolean[]{false, false}, weight, Order.ASC); } @Test public void testCompositeAndMixedIndexing() { PropertyKey name = makeKey("name", String.class); PropertyKey weight = makeKey("weight", Double.class); 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) .addKey(text, getTextMapping()).buildMixedIndex(INDEX); mixed.name(); composite.name(); finishSchema(); final int numV = 100; String[] strs = {"houseboat", "humanoid", "differential", "extraordinary"}; String[] strs2 = new String[strs.length]; for (int i = 0; i < strs.length; i++) strs2[i] = strs[i] + " " + strs[i]; final int modulo = 5; final int divisor = modulo * strs.length; for (int i = 0; i < numV; i++) { JanusGraphVertex v = tx.addVertex(); v.property("name", strs[i % strs.length]); v.property("text", strs[i % strs.length]); v.property("weight", (i % modulo) + 0.5); v.property("flag", true); } evaluateQuery(tx.query().has("name", Cmp.EQUAL, strs[0]), ElementCategory.VERTEX, numV / strs.length, new boolean[]{false, true}); evaluateQuery(tx.query().has("text", Text.CONTAINS, strs[0]), ElementCategory.VERTEX, numV / strs.length, new boolean[]{true, true}, mixed.name()); evaluateQuery(tx.query().has("text", Text.CONTAINS, strs[0]).has("flag"), ElementCategory.VERTEX, numV / strs.length, new boolean[]{false, true}, mixed.name()); evaluateQuery(tx.query().has("name", Cmp.EQUAL, strs[0]).has("weight", Cmp.EQUAL, 1.5), ElementCategory.VERTEX, numV / divisor, new boolean[]{true, true}, composite.name()); evaluateQuery(tx.query().has("name", Cmp.EQUAL, strs[0]).has("weight", Cmp.EQUAL, 1.5).has("flag"), ElementCategory.VERTEX, numV / divisor, new boolean[]{false, true}, composite.name()); evaluateQuery(tx.query().has("text", Text.CONTAINS, strs[2]).has("weight", Cmp.EQUAL, 2.5), ElementCategory.VERTEX, numV / divisor, new boolean[]{true, true}, mixed.name()); evaluateQuery(tx.query().has("text", Text.CONTAINS, strs[2]).has("weight", Cmp.EQUAL, 2.5).has("flag"), ElementCategory.VERTEX, numV / divisor, new boolean[]{false, true}, mixed.name()); evaluateQuery(tx.query().has("text", Text.CONTAINS, strs[3]).has("name", Cmp.EQUAL, strs[3]).has("weight", Cmp.EQUAL, 3.5), ElementCategory.VERTEX, numV / divisor, new boolean[]{true, true}, mixed.name(), composite.name()); evaluateQuery(tx.query().has("text", Text.CONTAINS, strs[3]).has("name", Cmp.EQUAL, strs[3]).has("weight", Cmp.EQUAL, 3.5).has("flag"), ElementCategory.VERTEX, numV / divisor, new boolean[]{false, true}, mixed.name(), composite.name()); clopen(); //Same queries as above evaluateQuery(tx.query().has("name", Cmp.EQUAL, strs[0]), ElementCategory.VERTEX, numV / strs.length, new boolean[]{false, true}); evaluateQuery(tx.query().has("text", Text.CONTAINS, strs[0]), ElementCategory.VERTEX, numV / strs.length, new boolean[]{true, true}, mixed.name()); evaluateQuery(tx.query().has("text", Text.CONTAINS, strs[0]).has("flag"), ElementCategory.VERTEX, numV / strs.length, new boolean[]{false, true}, mixed.name()); evaluateQuery(tx.query().has("name", Cmp.EQUAL, strs[0]).has("weight", Cmp.EQUAL, 1.5), ElementCategory.VERTEX, numV / divisor, new boolean[]{true, true}, composite.name()); evaluateQuery(tx.query().has("name", Cmp.EQUAL, strs[0]).has("weight", Cmp.EQUAL, 1.5).has("flag"), ElementCategory.VERTEX, numV / divisor, new boolean[]{false, true}, composite.name()); evaluateQuery(tx.query().has("text", Text.CONTAINS, strs[2]).has("weight", Cmp.EQUAL, 2.5), ElementCategory.VERTEX, numV / divisor, new boolean[]{true, true}, mixed.name()); evaluateQuery(tx.query().has("text", Text.CONTAINS, strs[2]).has("weight", Cmp.EQUAL, 2.5).has("flag"), ElementCategory.VERTEX, numV / divisor, new boolean[]{false, true}, mixed.name()); evaluateQuery(tx.query().has("text", Text.CONTAINS, strs[3]).has("name", Cmp.EQUAL, strs[3]).has("weight", Cmp.EQUAL, 3.5), ElementCategory.VERTEX, numV / divisor, new boolean[]{true, true}, mixed.name(), composite.name()); evaluateQuery(tx.query().has("text", Text.CONTAINS, strs[3]).has("name", Cmp.EQUAL, strs[3]).has("weight", Cmp.EQUAL, 3.5).has("flag"), ElementCategory.VERTEX, numV / divisor, new boolean[]{false, true}, mixed.name(), composite.name()); } private void setupChainGraph(int numV, String[] strs, 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); 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); mgmt.addIndexKey(vindex, text, getTextMapping(), Parameter.of("mapped-name", "xtext")); mgmt.addIndexKey(eindex, text, getTextMapping()); mgmt.addIndexKey(pindex, text, getTextMapping()); mgmt.makeEdgeLabel("knows").signature(name).make(); mgmt.makePropertyKey("uid").dataType(String.class).signature(text).make(); finishSchema(); JanusGraphVertex previous = null; for (int i = 0; i < numV; i++) { JanusGraphVertex v = graph.addVertex("name", strs[i % strs.length], "text", strs[i % strs.length]); v.addEdge("knows", previous == null ? v : previous, "name", strs[i % strs.length], "text", strs[i % strs.length]); v.property("uid", "v" + i, "name", strs[i % strs.length], "text", strs[i % strs.length]); previous = v; } } /** * Tests index parameters (mapping and names) and various string predicates */ @Test public void testIndexParameters() { int numV = 1000; String[] strs = {"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, strs, false); evaluateQuery(graph.query().has("text", Text.CONTAINS, "ducks"), ElementCategory.VERTEX, numV / strs.length * 2, new boolean[]{true, true}, VINDEX); assertCount(numV / strs.length * 2, graph.query().has("text", Text.CONTAINS, "ducks").vertices()); assertCount(numV / strs.length * 2, graph.query().has("text", Text.CONTAINS, "farm").vertices()); assertCount(numV / strs.length, graph.query().has("text", Text.CONTAINS, "beautiful").vertices()); evaluateQuery(graph.query().has("text", Text.CONTAINS_PREFIX, "beauti"), ElementCategory.VERTEX, numV / strs.length, new boolean[]{true, true}, VINDEX); assertCount(numV / strs.length, graph.query().has("text", Text.CONTAINS_REGEX, "be[r]+y").vertices()); assertCount(0, graph.query().has("text", Text.CONTAINS, "lolipop").vertices()); assertCount(numV / strs.length, graph.query().has("name", Cmp.EQUAL, strs[1]).vertices()); assertCount(numV / strs.length, graph.query().has("name", Cmp.EQUAL, strs[1]).vertices()); assertCount(numV / strs.length * (strs.length - 1), graph.query().has("name", Cmp.NOT_EQUAL, strs[2]).vertices()); assertCount(0, graph.query().has("name", Cmp.EQUAL, "farm").vertices()); assertCount(numV / strs.length, graph.query().has("name", Text.PREFIX, "ducks").vertices()); assertCount(numV / strs.length * 2, graph.query().has("name", Text.REGEX, "(.*)ducks(.*)").vertices()); //Same queries for edges evaluateQuery(graph.query().has("text", Text.CONTAINS, "ducks"), ElementCategory.EDGE, numV / strs.length * 2, new boolean[]{true, true}, EINDEX); assertCount(numV / strs.length * 2, graph.query().has("text", Text.CONTAINS, "ducks").edges()); assertCount(numV / strs.length * 2, graph.query().has("text", Text.CONTAINS, "farm").edges()); assertCount(numV / strs.length, graph.query().has("text", Text.CONTAINS, "beautiful").edges()); evaluateQuery(graph.query().has("text", Text.CONTAINS_PREFIX, "beauti"), ElementCategory.EDGE, numV / strs.length, new boolean[]{true, true}, EINDEX); assertCount(numV / strs.length, graph.query().has("text", Text.CONTAINS_REGEX, "be[r]+y").edges()); assertCount(0, graph.query().has("text", Text.CONTAINS, "lolipop").edges()); assertCount(numV / strs.length, graph.query().has("name", Cmp.EQUAL, strs[1]).edges()); assertCount(numV / strs.length, graph.query().has("name", Cmp.EQUAL, strs[1]).edges()); assertCount(numV / strs.length * (strs.length - 1), graph.query().has("name", Cmp.NOT_EQUAL, strs[2]).edges()); assertCount(0, graph.query().has("name", Cmp.EQUAL, "farm").edges()); assertCount(numV / strs.length, graph.query().has("name", Text.PREFIX, "ducks").edges()); assertCount(numV / strs.length * 2, graph.query().has("name", Text.REGEX, "(.*)ducks(.*)").edges()); //Same queries for properties evaluateQuery(graph.query().has("text", Text.CONTAINS, "ducks"), ElementCategory.PROPERTY, numV / strs.length * 2, new boolean[]{true, true}, PINDEX); assertCount(numV / strs.length * 2, graph.query().has("text", Text.CONTAINS, "ducks").properties()); assertCount(numV / strs.length * 2, graph.query().has("text", Text.CONTAINS, "farm").properties()); assertCount(numV / strs.length, graph.query().has("text", Text.CONTAINS, "beautiful").properties()); evaluateQuery(graph.query().has("text", Text.CONTAINS_PREFIX, "beauti"), ElementCategory.PROPERTY, numV / strs.length, new boolean[]{true, true}, PINDEX); assertCount(numV / strs.length, graph.query().has("text", Text.CONTAINS_REGEX, "be[r]+y").properties()); assertCount(0, graph.query().has("text", Text.CONTAINS, "lolipop").properties()); assertCount(numV / strs.length, graph.query().has("name", Cmp.EQUAL, strs[1]).properties()); assertCount(numV / strs.length, graph.query().has("name", Cmp.EQUAL, strs[1]).properties()); assertCount(numV / strs.length * (strs.length - 1), graph.query().has(LABEL_NAME, "uid").has("name", Cmp.NOT_EQUAL, strs[2]).properties()); assertCount(0, graph.query().has("name", Cmp.EQUAL, "farm").properties()); assertCount(numV / strs.length, graph.query().has("name", Text.PREFIX, "ducks").properties()); assertCount(numV / strs.length * 2, graph.query().has("name", Text.REGEX, "(.*)ducks(.*)").properties()); clopen(); /* ======================================= Same queries as above but against backend */ evaluateQuery(graph.query().has("text", Text.CONTAINS, "ducks"), ElementCategory.VERTEX, numV / strs.length * 2, new boolean[]{true, true}, VINDEX); assertCount(numV / strs.length * 2, graph.query().has("text", Text.CONTAINS, "ducks").vertices()); assertCount(numV / strs.length * 2, graph.query().has("text", Text.CONTAINS, "farm").vertices()); assertCount(numV / strs.length, graph.query().has("text", Text.CONTAINS, "beautiful").vertices()); evaluateQuery(graph.query().has("text", Text.CONTAINS_PREFIX, "beauti"), ElementCategory.VERTEX, numV / strs.length, new boolean[]{true, true}, VINDEX); assertCount(numV / strs.length, graph.query().has("text", Text.CONTAINS_REGEX, "be[r]+y").vertices()); assertCount(0, graph.query().has("text", Text.CONTAINS, "lolipop").vertices()); assertCount(numV / strs.length, graph.query().has("name", Cmp.EQUAL, strs[1]).vertices()); assertCount(numV / strs.length, graph.query().has("name", Cmp.EQUAL, strs[1]).vertices()); assertCount(numV / strs.length * (strs.length - 1), graph.query().has("name", Cmp.NOT_EQUAL, strs[2]).vertices()); assertCount(0, graph.query().has("name", Cmp.EQUAL, "farm").vertices()); assertCount(numV / strs.length, graph.query().has("name", Text.PREFIX, "ducks").vertices()); assertCount(numV / strs.length * 2, graph.query().has("name", Text.REGEX, "(.*)ducks(.*)").vertices()); //Same queries for edges evaluateQuery(graph.query().has("text", Text.CONTAINS, "ducks"), ElementCategory.EDGE, numV / strs.length * 2, new boolean[]{true, true}, EINDEX); assertCount(numV / strs.length * 2, graph.query().has("text", Text.CONTAINS, "ducks").edges()); assertCount(numV / strs.length * 2, graph.query().has("text", Text.CONTAINS, "farm").edges()); assertCount(numV / strs.length, graph.query().has("text", Text.CONTAINS, "beautiful").edges()); evaluateQuery(graph.query().has("text", Text.CONTAINS_PREFIX, "beauti"), ElementCategory.EDGE, numV / strs.length, new boolean[]{true, true}, EINDEX); assertCount(numV / strs.length, graph.query().has("text", Text.CONTAINS_REGEX, "be[r]+y").edges()); assertCount(0, graph.query().has("text", Text.CONTAINS, "lolipop").edges()); assertCount(numV / strs.length, graph.query().has("name", Cmp.EQUAL, strs[1]).edges()); assertCount(numV / strs.length, graph.query().has("name", Cmp.EQUAL, strs[1]).edges()); assertCount(numV / strs.length * (strs.length - 1), graph.query().has("name", Cmp.NOT_EQUAL, strs[2]).edges()); assertCount(0, graph.query().has("name", Cmp.EQUAL, "farm").edges()); assertCount(numV / strs.length, graph.query().has("name", Text.PREFIX, "ducks").edges()); assertCount(numV / strs.length * 2, graph.query().has("name", Text.REGEX, "(.*)ducks(.*)").edges()); //Same queries for properties evaluateQuery(graph.query().has("text", Text.CONTAINS, "ducks"), ElementCategory.PROPERTY, numV / strs.length * 2, new boolean[]{true, true}, PINDEX); assertCount(numV / strs.length * 2, graph.query().has("text", Text.CONTAINS, "ducks").properties()); assertCount(numV / strs.length * 2, graph.query().has("text", Text.CONTAINS, "farm").properties()); assertCount(numV / strs.length, graph.query().has("text", Text.CONTAINS, "beautiful").properties()); evaluateQuery(graph.query().has("text", Text.CONTAINS_PREFIX, "beauti"), ElementCategory.PROPERTY, numV / strs.length, new boolean[]{true, true}, PINDEX); assertCount(numV / strs.length, graph.query().has("text", Text.CONTAINS_REGEX, "be[r]+y").properties()); assertCount(0, graph.query().has("text", Text.CONTAINS, "lolipop").properties()); assertCount(numV / strs.length, graph.query().has("name", Cmp.EQUAL, strs[1]).properties()); assertCount(numV / strs.length, graph.query().has("name", Cmp.EQUAL, strs[1]).properties()); assertCount(numV / strs.length * (strs.length - 1), graph.query().has(LABEL_NAME, "uid").has("name", Cmp.NOT_EQUAL, strs[2]).properties()); assertCount(0, graph.query().has("name", Cmp.EQUAL, "farm").properties()); assertCount(numV / strs.length, graph.query().has("name", Text.PREFIX, "ducks").properties()); assertCount(numV / strs.length * 2, graph.query().has("name", Text.REGEX, "(.*)ducks(.*)").properties()); //Test name mapping if (supportsLuceneStyleQueries()) { assertCount(numV / strs.length * 2, graph.indexQuery(VINDEX, "xtext:ducks").vertices()); assertCount(0, graph.indexQuery(EINDEX, "xtext:ducks").edges()); } } /** * Tests index parameters (mapping and names) with raw indexQuery */ @Test public void testRawQueries() { if (!supportsLuceneStyleQueries()) return; int numV = 1000; String[] strs = {"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, strs, true); clopen(); assertCount(numV / strs.length * 2, graph.indexQuery(VINDEX, "v.text:ducks").vertices()); assertCount(numV / strs.length * 2, graph.indexQuery(VINDEX, "v.text:(farm uncle berry)").vertices()); assertCount(numV / strs.length, graph.indexQuery(VINDEX, "v.text:(farm uncle berry) AND v.name:\"Uncle Berry has a farm\"").vertices()); assertCount(numV / strs.length * 2, graph.indexQuery(VINDEX, "v.text:(beautiful are ducks)").vertices()); assertCount(numV / strs.length * 2 - 10, graph.indexQuery(VINDEX, "v.text:(beautiful are ducks)").offset(10).vertices()); assertCount(10, graph.indexQuery(VINDEX, "v.\"text\":(beautiful are ducks)").limit(10).vertices()); assertCount(10, graph.indexQuery(VINDEX, "v.\"text\":(beautiful are ducks)").limit(10).offset(10).vertices()); assertCount(0, graph.indexQuery(VINDEX, "v.\"text\":(beautiful are ducks)").limit(10).offset(numV).vertices()); //Test name mapping assertCount(numV / strs.length * 2, graph.indexQuery(VINDEX, "xtext:ducks").vertices()); assertCount(0, graph.indexQuery(VINDEX, "text:ducks").vertices()); //Test custom element identifier assertCount(numV / strs.length * 2, graph.indexQuery(VINDEX, "$v$text:ducks").setElementIdentifier("$v$").vertices()); //assertCount(0, graph.indexQuery(VINDEX, "v.\"text\":ducks").setElementIdentifier("$v$").vertices())); //Same queries for edges assertCount(numV / strs.length * 2, graph.indexQuery(EINDEX, "e.text:ducks").edges()); assertCount(numV / strs.length * 2, graph.indexQuery(EINDEX, "e.text:(farm uncle berry)").edges()); assertCount(numV / strs.length, graph.indexQuery(EINDEX, "e.text:(farm uncle berry) AND e.name:\"Uncle Berry has a farm\"").edges()); assertCount(numV / strs.length * 2, graph.indexQuery(EINDEX, "e.text:(beautiful are ducks)").edges()); assertCount(numV / strs.length * 2 - 10, graph.indexQuery(EINDEX, "e.text:(beautiful are ducks)").offset(10).edges()); assertCount(10, graph.indexQuery(EINDEX, "e.\"text\":(beautiful are ducks)").limit(10).edges()); assertCount(10, graph.indexQuery(EINDEX, "e.\"text\":(beautiful are ducks)").limit(10).offset(10).edges()); assertCount(0, graph.indexQuery(EINDEX, "e.\"text\":(beautiful are ducks)").limit(10).offset(numV).edges()); //Test name mapping assertCount(numV / strs.length * 2, graph.indexQuery(EINDEX, "text:ducks").edges()); //Same queries for edges assertCount(numV / strs.length * 2, graph.indexQuery(PINDEX, "p.text:ducks").properties()); assertCount(numV / strs.length * 2, graph.indexQuery(PINDEX, "p.text:(farm uncle berry)").properties()); assertCount(numV / strs.length, graph.indexQuery(PINDEX, "p.text:(farm uncle berry) AND p.name:\"Uncle Berry has a farm\"").properties()); assertCount(numV / strs.length * 2, graph.indexQuery(PINDEX, "p.text:(beautiful are ducks)").properties()); assertCount(numV / strs.length * 2 - 10, graph.indexQuery(PINDEX, "p.text:(beautiful are ducks)").offset(10).properties()); assertCount(10, graph.indexQuery(PINDEX, "p.\"text\":(beautiful are ducks)").limit(10).properties()); assertCount(10, graph.indexQuery(PINDEX, "p.\"text\":(beautiful are ducks)").limit(10).offset(10).properties()); assertCount(0, graph.indexQuery(PINDEX, "p.\"text\":(beautiful are ducks)").limit(10).offset(numV).properties()); //Test name mapping assertCount(numV / strs.length * 2, graph.indexQuery(PINDEX, "text:ducks").properties()); } @Test 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); mixed.name(); finishSchema(); tx.addVertex("name", "Long John Don"); tx.addVertex("name", "Long Little Lewis"); tx.addVertex("name", "Middle Sister Mabel"); clopen(); evaluateQuery(tx.query().has("name", Cmp.EQUAL, "Long John Don"), ElementCategory.VERTEX, 1, new boolean[]{true, true}, "mixed"); evaluateQuery(tx.query().has("name", Text.CONTAINS, "Long"), ElementCategory.VERTEX, 2, new boolean[]{true, true}, "mixed"); evaluateQuery(tx.query().has("name", Text.CONTAINS, "Long Don"), ElementCategory.VERTEX, 1, new boolean[]{true, true}, "mixed"); evaluateQuery(tx.query().has("name", Text.CONTAINS_PREFIX, "Lon"), ElementCategory.VERTEX, 2, new boolean[]{true, true}, "mixed"); evaluateQuery(tx.query().has("name", Text.CONTAINS_REGEX, "Lit*le"), ElementCategory.VERTEX, 1, new boolean[]{true, true}, "mixed"); evaluateQuery(tx.query().has("name", Text.REGEX, "Long.*"), ElementCategory.VERTEX, 2, new boolean[]{true, true}, "mixed"); evaluateQuery(tx.query().has("name", Text.PREFIX, "Middle"), ElementCategory.VERTEX, 1, new boolean[]{true, true}, "mixed"); evaluateQuery(tx.query().has("name", Text.FUZZY, "Long john Don"), ElementCategory.VERTEX, 1, new boolean[]{true, true}, "mixed"); evaluateQuery(tx.query().has("name", Text.CONTAINS_FUZZY, "Midle"), ElementCategory.VERTEX, 1, new boolean[]{true, true}, "mixed"); for (Vertex u : tx.getVertices()) { String n = u.<String>value("name"); if (n.endsWith("Don")) { u.remove(); } else if (n.endsWith("Lewis")) { u.property(VertexProperty.Cardinality.single, "name", "Big Brother Bob"); } else if (n.endsWith("Mabel")) { u.property("name").remove(); } } clopen(); evaluateQuery(tx.query().has("name", Text.CONTAINS, "Long"), ElementCategory.VERTEX, 0, new boolean[]{true, true}, "mixed"); evaluateQuery(tx.query().has("name", Text.CONTAINS, "Big"), ElementCategory.VERTEX, 1, new boolean[]{true, true}, "mixed"); evaluateQuery(tx.query().has("name", Text.PREFIX, "Big"), ElementCategory.VERTEX, 1, new boolean[]{true, true}, "mixed"); evaluateQuery(tx.query().has("name", Text.PREFIX, "Middle"), ElementCategory.VERTEX, 0, new boolean[]{true, true}, "mixed"); } @Category({BrittleTests.class}) @Test public void testIndexReplay() throws Exception { final TimestampProvider times = graph.getConfiguration().getTimestampProvider(); final Instant startTime = times.getTime(); clopen(option(SYSTEM_LOG_TRANSACTIONS), true , option(KCVSLog.LOG_READ_LAG_TIME, TRANSACTION_LOG), Duration.ofMillis(50) , option(LOG_READ_INTERVAL, TRANSACTION_LOG), Duration.ofMillis(250) , option(MAX_COMMIT_TIME), Duration.ofSeconds(1) , option(STORAGE_WRITE_WAITTIME), Duration.ofMillis(300) , option(TestMockIndexProvider.INDEX_BACKEND_PROXY, INDEX), readConfig.get(INDEX_BACKEND, INDEX) , option(INDEX_BACKEND, INDEX), TestMockIndexProvider.class.getName() , 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(); mgmt.buildIndex("mi", Vertex.class).addKey(name, getTextMapping()).addKey(age).buildMixedIndex(INDEX); finishSchema(); Vertex vs[] = new JanusGraphVertex[4]; vs[0] = tx.addVertex("name", "Big Boy Bobson", "age", 55); newTx(); vs[1] = tx.addVertex("name", "Long Little Lewis", "age", 35); vs[2] = tx.addVertex("name", "Tall Long Tiger", "age", 75); vs[3] = tx.addVertex("name", "Long John Don", "age", 15); newTx(); vs[2] = getV(tx, vs[2]); vs[2].remove(); vs[3] = getV(tx, vs[3]); vs[3].property(VertexProperty.Cardinality.single, "name", "Bad Boy Badsy"); vs[3].property("age").remove(); newTx(); vs[0] = getV(tx, vs[0]); vs[0].property(VertexProperty.Cardinality.single, "age", 66); newTx(); clopen(); //Just to make sure nothing has been persisted to index evaluateQuery(tx.query().has("name", Text.CONTAINS, "boy"), ElementCategory.VERTEX, 0, new boolean[]{true, true}, "mi"); /* Transaction Recovery */ TransactionRecovery recovery = JanusGraphFactory.startTransactionRecovery(graph, startTime); //wait Thread.sleep(12000L); recovery.shutdown(); long[] recoveryStats = ((StandardTransactionLogProcessor) recovery).getStatistics(); clopen(); evaluateQuery(tx.query().has("name", Text.CONTAINS, "boy"), ElementCategory.VERTEX, 2, new boolean[]{true, true}, "mi"); evaluateQuery(tx.query().has("name", Text.CONTAINS, "long"), ElementCategory.VERTEX, 1, new boolean[]{true, true}, "mi"); // JanusGraphVertex v = Iterables.getOnlyElement(tx.query().has("name",Text.CONTAINS,"long").vertices()); // System.out.println(v.getProperty("age")); evaluateQuery(tx.query().has("name", Text.CONTAINS, "long").interval("age", 30, 40), ElementCategory.VERTEX, 1, new boolean[]{true, true}, "mi"); evaluateQuery(tx.query().has("age", 75), ElementCategory.VERTEX, 0, new boolean[]{true, true}, "mi"); evaluateQuery(tx.query().has("name", Text.CONTAINS, "boy").interval("age", 60, 70), ElementCategory.VERTEX, 1, new boolean[]{true, true}, "mi"); evaluateQuery(tx.query().interval("age", 0, 100), ElementCategory.VERTEX, 2, new boolean[]{true, true}, "mi"); assertEquals(1, recoveryStats[0]); //schema transaction was successful assertEquals(4, recoveryStats[1]); //all 4 index transaction had provoked errors in the indexing backend } @Test public void testIndexUpdatesWithoutReindex() throws InterruptedException, ExecutionException { 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) }; clopen(settings); final String defText = "Mountain rocks are great friends"; final int defTime = 5; final double defHeight = 101.1; final String[] defPhones = new String[]{"1234", "5678"}; //Creates types and index only two keys key mgmt.makePropertyKey("time").dataType(Integer.class).make(); PropertyKey text = mgmt.makePropertyKey("text").dataType(String.class).make(); mgmt.makePropertyKey("height").dataType(Double.class).make(); if (indexFeatures.supportsCardinality(Cardinality.LIST)) { mgmt.makePropertyKey("phone").dataType(String.class).cardinality(Cardinality.LIST).make(); } mgmt.buildIndex("theIndex", Vertex.class).addKey(text, getTextMapping(), getFieldMap(text)).buildMixedIndex(INDEX); finishSchema(); //Add initial data addVertex(defTime, defText, defHeight, defPhones); //Indexes should not yet be in use clopen(settings); evaluateQuery(tx.query().has("text", Text.CONTAINS, "rocks"), ElementCategory.VERTEX, 1, new boolean[]{true, true}, "theIndex"); evaluateQuery(tx.query().has("time", 5), ElementCategory.VERTEX, 1, new boolean[]{false, true}); evaluateQuery(tx.query().interval("height", 100, 200), ElementCategory.VERTEX, 1, new boolean[]{false, true}); evaluateQuery(tx.query().interval("height", 100, 200).has("time", 5), ElementCategory.VERTEX, 1, new boolean[]{false, true}); evaluateQuery(tx.query().has("text", Text.CONTAINS, "rocks").has("time", 5).interval("height", 100, 200), ElementCategory.VERTEX, 1, new boolean[]{false, true}, "theIndex"); if (indexFeatures.supportsCardinality(Cardinality.LIST)) { evaluateQuery(tx.query().has("phone", Cmp.EQUAL, "1234"), ElementCategory.VERTEX, 1, new boolean[]{false, true}); evaluateQuery(tx.query().has("phone", Cmp.EQUAL, "5678"), ElementCategory.VERTEX, 1, new boolean[]{false, true}); } newTx(); //Add another key to index ------------------------------------------------------ finishSchema(); PropertyKey time = mgmt.getPropertyKey("time"); mgmt.addIndexKey(mgmt.getGraphIndex("theIndex"), time, getFieldMap(time)); finishSchema(); newTx(); //Add more data addVertex(defTime, defText, defHeight, defPhones); tx.commit(); //Should not yet be able to enable since not yet registered assertNull(mgmt.updateIndex(mgmt.getGraphIndex("theIndex"), SchemaAction.ENABLE_INDEX)); //This call is redundant and just here to make sure it doesn't mess anything up mgmt.updateIndex(mgmt.getGraphIndex("theIndex"), SchemaAction.REGISTER_INDEX).get(); mgmt.commit(); ManagementSystem.awaitGraphIndexStatus(graph, "theIndex").timeout(10L, ChronoUnit.SECONDS).call(); finishSchema(); mgmt.updateIndex(mgmt.getGraphIndex("theIndex"), SchemaAction.ENABLE_INDEX).get(); finishSchema(); //Add more data addVertex(defTime, defText, defHeight, defPhones); //One more key should be indexed but only sees partial data clopen(settings); evaluateQuery(tx.query().has("text", Text.CONTAINS, "rocks"), ElementCategory.VERTEX, 3, new boolean[]{true, true}, "theIndex"); evaluateQuery(tx.query().has("time", 5), ElementCategory.VERTEX, 2, new boolean[]{true, true}, "theIndex"); evaluateQuery(tx.query().interval("height", 100, 200), ElementCategory.VERTEX, 3, new boolean[]{false, true}); evaluateQuery(tx.query().interval("height", 100, 200).has("time", 5), ElementCategory.VERTEX, 2, new boolean[]{false, true}, "theIndex"); evaluateQuery(tx.query().has("text", Text.CONTAINS, "rocks").has("time", 5).interval("height", 100, 200), ElementCategory.VERTEX, 2, new boolean[]{false, true}, "theIndex"); if (indexFeatures.supportsCardinality(Cardinality.LIST)) { evaluateQuery(tx.query().has("phone", Cmp.EQUAL, "1234"), ElementCategory.VERTEX, 3, new boolean[]{false, true}); evaluateQuery(tx.query().has("phone", Cmp.EQUAL, "5678"), ElementCategory.VERTEX, 3, new boolean[]{false, true}); } newTx(); //Add another key to index ------------------------------------------------------ finishSchema(); PropertyKey height = mgmt.getPropertyKey("height"); mgmt.addIndexKey(mgmt.getGraphIndex("theIndex"), height); if (indexFeatures.supportsCardinality(Cardinality.LIST)) { PropertyKey phone = mgmt.getPropertyKey("phone"); mgmt.addIndexKey(mgmt.getGraphIndex("theIndex"), phone, new Parameter("mapping", Mapping.STRING)); } finishSchema(); //Add more data addVertex(defTime, defText, defHeight, defPhones); tx.commit(); mgmt.commit(); ManagementUtil.awaitGraphIndexUpdate(graph, "theIndex", 10, ChronoUnit.SECONDS); finishSchema(); mgmt.updateIndex(mgmt.getGraphIndex("theIndex"), SchemaAction.ENABLE_INDEX); finishSchema(); JanusGraphIndex index = mgmt.getGraphIndex("theIndex"); for (PropertyKey key : index.getFieldKeys()) { assertEquals(SchemaStatus.ENABLED, index.getIndexStatus(key)); } //Add more data addVertex(defTime, defText, defHeight, defPhones); //One more key should be indexed but only sees partial data clopen(settings); evaluateQuery(tx.query().has("text", Text.CONTAINS, "rocks"), ElementCategory.VERTEX, 5, new boolean[]{true, true}, "theIndex"); evaluateQuery(tx.query().has("time", 5), ElementCategory.VERTEX, 4, new boolean[]{true, true}, "theIndex"); evaluateQuery(tx.query().interval("height", 100, 200), ElementCategory.VERTEX, 2, new boolean[]{true, true}, "theIndex"); evaluateQuery(tx.query().interval("height", 100, 200).has("time", 5), ElementCategory.VERTEX, 2, new boolean[]{true, true}, "theIndex"); evaluateQuery(tx.query().has("text", Text.CONTAINS, "rocks").has("time", 5).interval("height", 100, 200), ElementCategory.VERTEX, 2, new boolean[]{true, true}, "theIndex"); if (indexFeatures.supportsCardinality(Cardinality.LIST)) { evaluateQuery(tx.query().has("phone", Cmp.EQUAL, "1234"), ElementCategory.VERTEX, 2, new boolean[]{true, true}, "theIndex"); evaluateQuery(tx.query().has("phone", Cmp.EQUAL, "5678"), ElementCategory.VERTEX, 2, new boolean[]{true, true}, "theIndex"); } newTx(); finishSchema(); mgmt.updateIndex(mgmt.getGraphIndex("theIndex"), SchemaAction.REINDEX).get(); mgmt.commit(); finishSchema(); //All the data should now be in the index clopen(settings); evaluateQuery(tx.query().has("text", Text.CONTAINS, "rocks"), ElementCategory.VERTEX, 5, new boolean[]{true, true}, "theIndex"); evaluateQuery(tx.query().has("time", 5), ElementCategory.VERTEX, 5, new boolean[]{true, true}, "theIndex"); evaluateQuery(tx.query().interval("height", 100, 200), ElementCategory.VERTEX, 5, new boolean[]{true, true}, "theIndex"); evaluateQuery(tx.query().interval("height", 100, 200).has("time", 5), ElementCategory.VERTEX, 5, new boolean[]{true, true}, "theIndex"); evaluateQuery(tx.query().has("text", Text.CONTAINS, "rocks").has("time", 5).interval("height", 100, 200), ElementCategory.VERTEX, 5, new boolean[]{true, true}, "theIndex"); if (indexFeatures.supportsCardinality(Cardinality.LIST)) { evaluateQuery(tx.query().has("phone", Cmp.EQUAL, "1234"), ElementCategory.VERTEX, 5, new boolean[]{true, true}, "theIndex"); evaluateQuery(tx.query().has("phone", Cmp.EQUAL, "5678"), ElementCategory.VERTEX, 5, new boolean[]{true, true}, "theIndex"); } mgmt.updateIndex(mgmt.getGraphIndex("theIndex"), SchemaAction.DISABLE_INDEX).get(); tx.commit(); mgmt.commit(); ManagementUtil.awaitGraphIndexUpdate(graph, "theIndex", 10, ChronoUnit.SECONDS); finishSchema(); index = mgmt.getGraphIndex("theIndex"); for (PropertyKey key : index.getFieldKeys()) { assertEquals(SchemaStatus.DISABLED, index.getIndexStatus(key)); } newTx(); //This now requires a full graph scan evaluateQuery(tx.query().has("time", 5), ElementCategory.VERTEX, 5, new boolean[]{false, true}); } 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) { v.property("phone", phone); } newTx(); } /* ================================================================================== TIME-TO-LIVE ==================================================================================*/ @Test public void testVertexTTLWithMixedIndices() throws Exception { if (!features.hasCellTTL() || !indexFeatures.supportsDocumentTTL()) { return; } PropertyKey name = makeKey("name", String.class); PropertyKey time = makeKey("time", Long.class); PropertyKey text = makeKey("text", String.class); VertexLabel event = mgmt.makeVertexLabel("event").setStatic().make(); final int eventTTLSeconds = (int) TestGraphConfigs.getTTL(TimeUnit.SECONDS); mgmt.setTTL(event, Duration.ofSeconds(eventTTLSeconds)); mgmt.buildIndex("index1", Vertex.class). addKey(name, getStringMapping()).addKey(time).buildMixedIndex(INDEX); mgmt.buildIndex("index2", Vertex.class).indexOnly(event). addKey(text, getTextMapping()).buildMixedIndex(INDEX); assertEquals(Duration.ZERO, mgmt.getTTL(name)); assertEquals(Duration.ZERO, mgmt.getTTL(time)); assertEquals(Duration.ofSeconds(eventTTLSeconds), mgmt.getTTL(event)); finishSchema(); 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(); 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; v2.property(VertexProperty.Cardinality.single, "time", time2); evaluateQuery(tx.query().has("name", "first event").orderBy("time", decr), ElementCategory.VERTEX, 1, new boolean[]{true, true}, tx.getPropertyKey("time"), Order.DESC, "index1"); evaluateQuery(tx.query().has("text", Text.CONTAINS, "help").has(LABEL_NAME, "event"), ElementCategory.VERTEX, 1, new boolean[]{true, true}, "index2"); clopen(); Object v1Id = v1.id(); 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"); evaluateQuery(tx.query().has("text", Text.CONTAINS, "help").has(LABEL_NAME, "event"), ElementCategory.VERTEX, 1, new boolean[]{true, true}, "index2"); v1 = getV(tx, v1Id); v2 = getV(tx, v1Id); assertNotNull(v1); assertNotNull(v2); Thread.sleep(TimeUnit.MILLISECONDS.convert((long) Math.ceil(eventTTLSeconds * 1.25), TimeUnit.SECONDS)); clopen(); Thread.sleep(TimeUnit.MILLISECONDS.convert((long) Math.ceil(eventTTLSeconds * 1.25), TimeUnit.SECONDS)); evaluateQuery(tx.query().has("text", Text.CONTAINS, "help").has(LABEL_NAME, "event"), ElementCategory.VERTEX, 0, new boolean[]{true, true}, "index2"); evaluateQuery(tx.query().has("name", "first event").orderBy("time", decr), ElementCategory.VERTEX, 0, new boolean[]{true, true}, tx.getPropertyKey("time"), Order.DESC, "index1"); v1 = getV(tx, v1Id); v2 = getV(tx, v2Id); assertNull(v1); assertNull(v2); } @Test public void testEdgeTTLWithMixedIndices() throws Exception { if (!features.hasCellTTL() || !indexFeatures.supportsDocumentTTL()) { 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); EdgeLabel label = mgmt.makeEdgeLabel("likes").make(); final int likesTTLSeconds = (int) TestGraphConfigs.getTTL(TimeUnit.SECONDS); mgmt.setTTL(label, Duration.ofSeconds(likesTTLSeconds)); mgmt.buildIndex("index1", Edge.class). addKey(name, getStringMapping()).addKey(time).buildMixedIndex(INDEX); mgmt.buildIndex("index2", Edge.class).indexOnly(label). addKey(text, getTextMapping()).buildMixedIndex(INDEX); assertEquals(Duration.ZERO, mgmt.getTTL(name)); assertEquals(Duration.ofSeconds(likesTTLSeconds), mgmt.getTTL(label)); finishSchema(); 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(); e1.property("time", time1); Edge e2 = v2.addEdge("likes", v3, "name", "v2 likes v3", "text", "this won't match anything"); long time2 = time1 + 1; e2.property("time", time2); tx.commit(); clopen(); Object e1Id = e1.id(); e2.id(); evaluateQuery(tx.query().has("text", Text.CONTAINS, "help").has(LABEL_NAME, "likes"), ElementCategory.EDGE, 1, new boolean[]{true, true}, "index2"); evaluateQuery(tx.query().has("name", "v2 likes v3").orderBy("time", decr), ElementCategory.EDGE, 1, new boolean[]{true, true}, tx.getPropertyKey("time"), Order.DESC, "index1"); v1 = getV(tx, v1.id()); v2 = getV(tx, v2.id()); v3 = getV(tx, v3.id()); e1 = getE(tx, e1Id); e2 = getE(tx, e1Id); assertNotNull(v1); assertNotNull(v2); assertNotNull(v3); assertNotNull(e1); assertNotNull(e2); assertNotEmpty(v1.query().direction(Direction.OUT).edges()); assertNotEmpty(v2.query().direction(Direction.OUT).edges()); Thread.sleep(TimeUnit.MILLISECONDS.convert((long) Math.ceil(likesTTLSeconds * 1.25), TimeUnit.SECONDS)); clopen(); // ...indexes have expired evaluateQuery(tx.query().has("text", Text.CONTAINS, "help").has(LABEL_NAME, "likes"), ElementCategory.EDGE, 0, new boolean[]{true, true}, "index2"); evaluateQuery(tx.query().has("name", "v2 likes v3").orderBy("time", decr), ElementCategory.EDGE, 0, new boolean[]{true, true}, tx.getPropertyKey("time"), Order.DESC, "index1"); v1 = getV(tx, v1.id()); v2 = getV(tx, v2.id()); v3 = getV(tx, v3.id()); e1 = getE(tx, e1Id); e2 = getE(tx, e1Id); assertNotNull(v1); assertNotNull(v2); assertNotNull(v3); // edges have expired from the graph... assertNull(e1); assertNull(e2); assertEmpty(v1.query().direction(Direction.OUT).edges()); assertEmpty(v2.query().direction(Direction.OUT).edges()); } /* ================================================================================== SPECIAL CONCURRENT UPDATE CASES ==================================================================================*/ /** * Create a vertex with an indexed property and commit. Open two new * transactions; delete vertex in one and delete just the property in the * other, then commit in the same order. Neither commit throws an exception. */ @Test public void testDeleteVertexThenDeleteProperty() throws BackendException { testNestedWrites("x", null); } /** * Create a vertex and commit. Open two new transactions; delete vertex in * one and add an indexed property in the other, then commit in the same * order. Neither commit throws an exception. */ @Test public void testDeleteVertexThenAddProperty() throws BackendException { testNestedWrites(null, "y"); } /** * Create a vertex with an indexed property and commit. Open two new * transactions; delete vertex in one and modify the property in the other, * then commit in the same order. Neither commit throws an exception. */ @Test public void testDeleteVertexThenModifyProperty() throws BackendException { testNestedWrites("x", "y"); } @Test public void testIndexQueryWithScore() throws InterruptedException { 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(); v1.property("text", "Hello Hello Hello Hello Hello Hello Hello Hello"); v2.property("text", "Hello abab abab fsdfsd sfdfsd sdffs fsdsdf fdf fsdfsd aera fsad abab abab fsdfsd sfdf"); v3.property("text", "Hello"); tx.commit(); Thread.sleep(5000); Set<Double> scores = new HashSet<Double>(); for (JanusGraphIndexQuery.Result<JanusGraphVertex> r : graph.indexQuery("store1", "v.text:(Hello)").vertices()) { scores.add(r.getScore()); } Assert.assertEquals(3, scores.size()); } @Test // this tests a case when there as AND with a single CONTAINS condition inside AND(name:(was here)) // 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); mgmt.buildIndex("store1", Vertex.class).addKey(name).buildMixedIndex(INDEX); mgmt.commit(); JanusGraphVertex v1 = tx.addVertex(); v1.property("name", "hercules was here"); tx.commit(); Thread.sleep(2000); JanusGraphVertex r = Iterables.<JanusGraphVertex>get(graph.query().has("name", Text.CONTAINS, "hercules here").vertices(), 0); Assert.assertEquals(r.property("name").value(), "hercules was here"); } private void testNestedWrites(String initialValue, String updatedValue) throws BackendException { // This method touches a single vertex with multiple transactions, // leading to deadlock under BDB and cascading test failures. Check for // the hasTxIsolation() store feature, which is currently true for BDB // but false for HBase/Cassandra. This is kind of a hack; a more robust // approach might implement different methods/assertions depending on // whether the store is capable of deadlocking or detecting conflicting // writes and aborting a transaction. Backend b = null; try { b = graph.getConfiguration().getBackend(); if (b.getStoreFeatures().hasTxIsolation()) { log.info("Skipping " + getClass().getSimpleName() + "." + methodName.getMethodName()); return; } } finally { if (null != b) b.close(); } final String propName = "foo"; // Write schema and one vertex PropertyKey prop = makeKey(propName, String.class); createExternalVertexIndex(prop, INDEX); finishSchema(); JanusGraphVertex v = graph.addVertex(); if (null != initialValue) v.property(VertexProperty.Cardinality.single, propName, initialValue); graph.tx().commit(); Object id = v.id(); // Open two transactions and modify the same vertex JanusGraphTransaction vertexDeleter = graph.newTransaction(); JanusGraphTransaction propDeleter = graph.newTransaction(); getV(vertexDeleter, id).remove(); if (null == updatedValue) getV(propDeleter, id).property(propName).remove(); else getV(propDeleter, id).property(VertexProperty.Cardinality.single, propName, updatedValue); vertexDeleter.commit(); propDeleter.commit(); // The vertex must not exist after deletion graph.tx().rollback(); assertEquals(null, getV(graph, id)); assertEmpty(graph.query().has(propName).vertices()); if (null != updatedValue) assertEmpty(graph.query().has(propName, updatedValue).vertices()); graph.tx().rollback(); } /** * Tests indexing using _all virtual field */ @Test public void testWidcardQuery() { if (supportsWildcardQuery()) { PropertyKey p1 = makeKey("p1", String.class); PropertyKey p2 = makeKey("p2", String.class); mgmt.buildIndex("mixedIndex", Vertex.class).addKey(p1).addKey(p2).buildMixedIndex(INDEX); finishSchema(); clopen(); JanusGraphVertex v1 = graph.addVertex(); v1.property("p1", "test1"); v1.property("p2", "test2"); clopen();//Flush the index assertEquals(v1, graph.indexQuery("mixedIndex", "v.*:\"test1\"").vertices().iterator().next().getElement()); assertEquals(v1, graph.indexQuery("mixedIndex", "v.*:\"test2\"").vertices().iterator().next().getElement()); } } /** * Tests indexing lists */ @Test public void testListIndexing() { testIndexing(Cardinality.LIST); } protected abstract boolean supportsCollections(); /** * Tests indexing sets */ @Test public void testSetIndexing() { testIndexing(Cardinality.SET); } 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(); mgmt.buildIndex("collectionIndex", Vertex.class).addKey(stringProperty, getStringMapping()).addKey(intProperty).addKey(longProperty).addKey(uuidProperty).addKey(geopointProperty).buildMixedIndex(INDEX); finishSchema(); testCollection(cardinality, "name", "Totoro", "Hiro"); testCollection(cardinality, "age", 1, 2); testCollection(cardinality, "long", 1L, 2L); testCollection(cardinality, "uuid", UUID.randomUUID(), UUID.randomUUID()); testCollection(cardinality, "geopoint", Geoshape.point(1.0, 1.0), Geoshape.point(2.0, 2.0)); } else { try { 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 e) { } } } private void testCollection(Cardinality cardinality, String property, Object value1, Object value2) { clopen(); Vertex v1 = graph.addVertex(); //Adding properties one at a time v1.property(property, value1); clopen();//Flush the index assertEquals(v1, getOnlyElement(graph.query().has(property, value1).vertices())); v1 = getV(graph, v1.id()); v1.property(property, value2); assertEquals(v1, getOnlyElement(graph.query().has(property, value1).vertices())); assertEquals(v1, getOnlyElement(graph.query().has(property, value2).vertices())); clopen();//Flush the index assertEquals(v1, getOnlyElement(graph.query().has(property, value1).vertices())); assertEquals(v1, getOnlyElement(graph.query().has(property, value2).vertices())); //Remove the properties v1 = getV(graph, v1.id()); v1.properties(property).forEachRemaining(p -> { if (p.value().equals(value1)) { p.remove(); } }); assertFalse(graph.query().has(property, value1).vertices().iterator().hasNext()); assertEquals(v1, getOnlyElement(graph.query().has(property, value2).vertices())); clopen();//Flush the index assertEquals(v1, getOnlyElement(graph.query().has(property, value2).vertices())); assertFalse(graph.query().has(property, value1).vertices().iterator().hasNext()); //Re add the properties v1 = getV(graph, v1.id()); v1.property(property, value1); assertEquals(v1, getOnlyElement(graph.query().has(property, value1).vertices())); assertEquals(v1, getOnlyElement(graph.query().has(property, value2).vertices())); clopen();//Flush the index assertEquals(v1, getOnlyElement(graph.query().has(property, value1).vertices())); assertEquals(v1, getOnlyElement(graph.query().has(property, value2).vertices())); //Add a duplicate property v1 = getV(graph, v1.id()); v1.property(property, value1); assertEquals(Cardinality.SET.equals(cardinality) ? 2 : 3, Iterators.size(getOnlyVertex(graph.query().has(property, value1)).properties(property))); clopen();//Flush the index assertEquals(Cardinality.SET.equals(cardinality) ? 2 : 3, Iterators.size(getOnlyVertex(graph.query().has(property, value1)).properties(property))); //Add two properties at once to a fresh vertex graph.vertices().forEachRemaining(v -> v.remove()); v1 = graph.addVertex(); v1.property(property, value1); v1.property(property, value2); assertEquals(v1, getOnlyElement(graph.query().has(property, value1).vertices())); assertEquals(v1, getOnlyElement(graph.query().has(property, value2).vertices())); clopen();//Flush the index assertEquals(v1, getOnlyElement(graph.query().has(property, value1).vertices())); assertEquals(v1, getOnlyElement(graph.query().has(property, value2).vertices())); //If this is a geo test then try a within query if (value1 instanceof Geoshape) { assertEquals(v1, getOnlyElement(graph.query().has(property, Geo.WITHIN, Geoshape.circle(1.0, 1.0, 0.1)).vertices())); assertEquals(v1, getOnlyElement(graph.query().has(property, Geo.WITHIN, Geoshape.circle(2.0, 2.0, 0.1)).vertices())); } } 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; 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()); assertCount(i + 1, tx.query().has(geoPointProperty, Geo.INTERSECT, Geoshape.circle(0.0, 0.0, distance)).vertices()); assertCount(i + 1, tx.query().has(geoPointProperty, Geo.INTERSECT, Geoshape.circle(0.0, 0.0, distance)).edges()); assertCount(numV-(i + 1), tx.query().has(geoPointProperty, Geo.DISJOINT, Geoshape.circle(0.0, 0.0, distance)).vertices()); assertCount(numV-(i + 1), tx.query().has(geoPointProperty, Geo.DISJOINT, Geoshape.circle(0.0, 0.0, distance)).edges()); assertCount(i + 1, tx.query().has(geoShapeProperty, Geo.INTERSECT, Geoshape.circle(0.0, 0.0, distance)).vertices()); assertCount(i + 1, tx.query().has(geoShapeProperty, Geo.INTERSECT, Geoshape.circle(0.0, 0.0, distance)).edges()); if (i > 0) { assertCount(i, tx.query().has(geoShapeProperty, Geo.WITHIN, Geoshape.circle(0.0, 0.0, distance-bufferKm)).vertices()); assertCount(i, tx.query().has(geoShapeProperty, Geo.WITHIN, Geoshape.circle(0.0, 0.0, distance-bufferKm)).edges()); } assertCount(numV-(i + 1), tx.query().has(geoShapeProperty, Geo.DISJOINT, Geoshape.circle(0.0, 0.0, distance)).vertices()); assertCount(numV-(i + 1), tx.query().has(geoShapeProperty, Geo.DISJOINT, Geoshape.circle(0.0, 0.0, distance)).edges()); assertCount(i % 2, tx.query().has(geoShapeProperty, Geo.CONTAINS, Geoshape.point(-offset,-offset)).vertices()); 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); 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()); assertCount(i + 1, tx.query().has(geoPointProperty, Geo.INTERSECT, bufferedBox).edges()); 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); assertCount(i, tx.query().has(geoShapeProperty, Geo.WITHIN, exactBox).vertices()); assertCount(i, tx.query().has(geoShapeProperty, Geo.WITHIN, exactBox).edges()); } assertCount(i + 1, tx.query().has(geoShapeProperty, Geo.INTERSECT, bufferedBox).vertices()); assertCount(i + 1, tx.query().has(geoShapeProperty, Geo.INTERSECT, bufferedBox).edges()); 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[][] {{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[][] {{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()); } assertCount(i + 1, tx.query().has(geoShapeProperty, Geo.INTERSECT, bufferedPoly).vertices()); assertCount(i + 1, tx.query().has(geoShapeProperty, Geo.INTERSECT, bufferedPoly).edges()); assertCount(numV-(i + 1), tx.query().has(geoShapeProperty, Geo.DISJOINT, bufferedPoly).vertices()); assertCount(numV-(i + 1), tx.query().has(geoShapeProperty, Geo.DISJOINT, bufferedPoly).edges()); } @Test public void shouldAwaitMultipleStatuses() throws InterruptedException, ExecutionException { PropertyKey key1 = makeKey("key1", String.class); 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); 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) { Assert.fail("Failed to awaitGraphIndexStatus on multiple statuses."); } } }