// 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.base.Preconditions; import com.google.common.collect.Iterables; import com.google.common.collect.Iterators; import org.janusgraph.core.*; import org.janusgraph.core.attribute.Cmp; import org.janusgraph.core.schema.ConsistencyModifier; import org.janusgraph.core.Multiplicity; import org.janusgraph.core.schema.JanusGraphIndex; import org.janusgraph.diskstorage.util.TestLockerManager; import org.janusgraph.graphdb.configuration.GraphDatabaseConfiguration; import org.janusgraph.testcategory.SerialTests; import org.apache.tinkerpop.gremlin.structure.Direction; import org.apache.tinkerpop.gremlin.structure.Edge; import static org.apache.tinkerpop.gremlin.structure.Direction.*; import org.apache.tinkerpop.gremlin.structure.Vertex; import org.apache.tinkerpop.gremlin.structure.VertexProperty; import org.junit.Test; import org.junit.experimental.categories.Category; import java.time.Duration; import java.time.Instant; import java.util.Iterator; import static org.janusgraph.testutil.JanusGraphAssert.assertCount; import static org.junit.Assert.*; import static org.junit.Assert.assertEquals; /** * @author Matthias Broecheler (me@matthiasb.com) */ @Category({ SerialTests.class }) public abstract class JanusGraphEventualGraphTest extends JanusGraphBaseTest { @Test public void verifyEligibility() { Preconditions.checkArgument(!graph.getConfiguration().getBackend().getStoreFeatures().hasTxIsolation(), "This test suite only applies to eventually consistent data stores"); } @Test public void concurrentIndexTest() { makeVertexIndexedUniqueKey("uid", String.class); makeVertexIndexedKey("value", Object.class); finishSchema(); tx.addVertex("uid", "v"); clopen(); //Concurrent index addition JanusGraphTransaction tx1 = graph.newTransaction(); JanusGraphTransaction tx2 = graph.newTransaction(); getVertex(tx1, "uid", "v").property(VertexProperty.Cardinality.single, "value", 11); getVertex(tx2, "uid", "v").property(VertexProperty.Cardinality.single, "value", 11); tx1.commit(); tx2.commit(); assertEquals("v", Iterators.<String>getOnlyElement(Iterables.<JanusGraphVertex>getOnlyElement(tx.query().has("value", 11).vertices()).values("uid"))); } /** * Tests the correct interpretation of the commit time and that timestamps can be read */ @Test public void testTimestampSetting() { clopen(option(GraphDatabaseConfiguration.STORE_META_TIMESTAMPS,"edgestore"),true, option(GraphDatabaseConfiguration.STORE_META_TTL,"edgestore"),true); // Transaction 1: Init graph with two vertices, having set "name" and "age" properties JanusGraphTransaction tx1 = graph.buildTransaction().commitTime(Instant.ofEpochSecond(100)).start(); String name = "name"; String age = "age"; String address = "address"; JanusGraphVertex v1 = tx1.addVertex(name, "a"); JanusGraphVertex v2 = tx1.addVertex(age, "14", name, "b", age, "42"); tx1.commit(); // Fetch vertex ids long id1 = getId(v1); long id2 = getId(v2); // Transaction 2: Remove "name" property from v1, set "address" property; create // an edge v2 -> v1 JanusGraphTransaction tx2 = graph.buildTransaction().commitTime(Instant.ofEpochSecond(1000)).start(); v1 = getV(tx2,id1); v2 = getV(tx2,id2); for (Iterator<VertexProperty<Object>> propiter = v1.properties(name); propiter.hasNext(); ) { VertexProperty prop = propiter.next(); if (features.hasTimestamps()) { Instant t = prop.value("~timestamp"); assertEquals(100,t.getEpochSecond()); assertEquals(Instant.ofEpochSecond(0, 1000).getNano(),t.getNano()); } if (features.hasCellTTL()) { Duration d = prop.value("~ttl"); assertEquals(0l, d.getSeconds()); assertTrue(d.isZero()); } } assertEquals(1, v1.query().propertyCount()); assertEquals(1, v1.query().has("~timestamp", Cmp.GREATER_THAN, Instant.ofEpochSecond(10)).propertyCount()); assertEquals(1, v1.query().has("~timestamp", Instant.ofEpochSecond(100, 1000)).propertyCount()); v1.property(name).remove(); v1.property(VertexProperty.Cardinality.single, address, "xyz"); Edge edge = v2.addEdge("parent",v1); tx2.commit(); Object edgeId = edge.id(); JanusGraphVertex afterTx2 = getV(graph,id1); // Verify that "name" property is gone assertFalse(afterTx2.keys().contains(name)); // Verify that "address" property is set assertEquals("xyz", afterTx2.value(address)); // Verify that the edge is properly registered with the endpoint vertex assertCount(1, afterTx2.query().direction(IN).labels("parent").edges()); // Verify that edge is registered under the id assertNotNull(getE(graph,edgeId)); graph.tx().commit(); // Transaction 3: Remove "address" property from v1 with earlier timestamp than // when the value was set JanusGraphTransaction tx3 = graph.buildTransaction().commitTime(Instant.ofEpochSecond(200)).start(); v1 = getV(tx3,id1); v1.property(address).remove(); tx3.commit(); JanusGraphVertex afterTx3 = getV(graph,id1); graph.tx().commit(); // Verify that "address" is still set assertEquals("xyz", afterTx3.value(address)); // Transaction 4: Modify "age" property on v2, remove edge between v2 and v1 JanusGraphTransaction tx4 = graph.buildTransaction().commitTime(Instant.ofEpochSecond(2000)).start(); v2 = getV(tx4,id2); v2.property(VertexProperty.Cardinality.single, age, "15"); getE(tx4,edgeId).remove(); tx4.commit(); JanusGraphVertex afterTx4 = getV(graph,id2); // Verify that "age" property is modified assertEquals("15", afterTx4.value(age)); // Verify that edge is no longer registered with the endpoint vertex assertCount(0, afterTx4.query().direction(OUT).labels("parent").edges()); // Verify that edge entry disappeared from id registry assertNull(getE(graph,edgeId)); // Transaction 5: Modify "age" property on v2 with earlier timestamp JanusGraphTransaction tx5 = graph.buildTransaction().commitTime(Instant.ofEpochSecond(1500)).start(); v2 = getV(tx5,id2); v2.property(VertexProperty.Cardinality.single, age, "16"); tx5.commit(); JanusGraphVertex afterTx5 = getV(graph,id2); // Verify that the property value is unchanged assertEquals("15", afterTx5.value(age)); } /** * Tests that timestamped edges can be updated */ @Test public void testTimestampedEdgeUpdates() { clopen(option(GraphDatabaseConfiguration.STORE_META_TIMESTAMPS, "edgestore"), true, option(GraphDatabaseConfiguration.STORE_META_TTL, "edgestore"), true); // Transaction 1: Init graph with two vertices and one edge JanusGraphTransaction tx = graph.buildTransaction().commitTime(Instant.ofEpochSecond(100)).start(); JanusGraphVertex v1 = tx.addVertex(); JanusGraphVertex v2 = tx.addVertex(); Edge e = v1.addEdge("related",v2); e.property("time", 25); tx.commit(); tx = graph.buildTransaction().commitTime(Instant.ofEpochSecond(200)).start(); v1 = tx.getVertex(v1.longId()); assertNotNull(v1); e = Iterators.getOnlyElement(v1.edges(Direction.OUT, "related")); assertNotNull(e); assertEquals(Integer.valueOf(25), e.value("time")); e.property("time", 125); tx.commit(); tx = graph.buildTransaction().commitTime(Instant.ofEpochSecond(300)).start(); v1 = tx.getVertex(v1.longId()); assertNotNull(v1); e = Iterators.getOnlyElement(v1.edges(Direction.OUT, "related")); assertEquals(Integer.valueOf(125), e.value("time")); e.remove(); tx.commit(); } /** * Tests that batch-loading will ignore locks */ @Test public void testBatchLoadingNoLock() { testBatchLoadingLocking(true); } /** * Tests that without batch-loading locks will be correctly applied (and therefore the tx fails) */ @Test public void testLockException() { try { testBatchLoadingLocking(false); fail(); } catch (JanusGraphException e) { Throwable cause = e; while (cause.getCause()!=null) cause=cause.getCause(); assertEquals(UnsupportedOperationException.class,cause.getClass()); } } public void testBatchLoadingLocking(boolean batchloading) { PropertyKey uid = makeKey("uid",Long.class); JanusGraphIndex uidIndex = mgmt.buildIndex("uid",Vertex.class).unique().addKey(uid).buildCompositeIndex(); mgmt.setConsistency(uid, ConsistencyModifier.LOCK); mgmt.setConsistency(uidIndex,ConsistencyModifier.LOCK); EdgeLabel knows = mgmt.makeEdgeLabel("knows").multiplicity(Multiplicity.ONE2ONE).make(); mgmt.setConsistency(knows,ConsistencyModifier.LOCK); finishSchema(); TestLockerManager.ERROR_ON_LOCKING=true; clopen(option(GraphDatabaseConfiguration.STORAGE_BATCH),batchloading, option(GraphDatabaseConfiguration.LOCK_BACKEND),"test"); int numV = 10000; for (int i=0;i<numV;i++) { JanusGraphVertex v = tx.addVertex("uid",i+1); v.addEdge("knows",v); } clopen(); for (int i=0;i<Math.min(numV,300);i++) { assertEquals(1, Iterables.size(graph.query().has("uid", i + 1).vertices())); JanusGraphVertex v = Iterables.<JanusGraphVertex>getOnlyElement(graph.query().has("uid", i + 1).vertices()); assertEquals(1, Iterables.size(v.query().direction(OUT).labels("knows").edges())); } } /** * Tests that consistency modes are correctly interpreted in the absence of locks (or tx isolation) */ @Test public void testConsistencyModifier() throws InterruptedException { makeKey("sig",Integer.class); makeKey("weight",Double.class); mgmt.makePropertyKey("name").dataType(String.class).cardinality(Cardinality.SET).make(); mgmt.makePropertyKey("value").dataType(Integer.class).cardinality(Cardinality.LIST).make(); PropertyKey valuef = mgmt.makePropertyKey("valuef").dataType(Integer.class).cardinality(Cardinality.LIST).make(); mgmt.setConsistency(valuef,ConsistencyModifier.FORK); mgmt.makeEdgeLabel("em").multiplicity(Multiplicity.MULTI).make(); EdgeLabel emf = mgmt.makeEdgeLabel("emf").multiplicity(Multiplicity.MULTI).make(); mgmt.setConsistency(emf,ConsistencyModifier.FORK); mgmt.makeEdgeLabel("es").multiplicity(Multiplicity.SIMPLE).make(); mgmt.makeEdgeLabel("o2o").multiplicity(Multiplicity.ONE2ONE).make(); mgmt.makeEdgeLabel("o2m").multiplicity(Multiplicity.ONE2MANY).make(); finishSchema(); JanusGraphVertex u = tx.addVertex(), v = tx.addVertex(); JanusGraphRelation[] rs = new JanusGraphRelation[9]; final int txid = 1; rs[0]=sign(v.property("weight",5.0),txid); rs[1]=sign(v.property("name","John"),txid); rs[2]=sign(v.property("value",2),txid); rs[3]=sign(v.property("valuef",2),txid); rs[6]=sign(v.addEdge("es",u),txid); rs[7]=sign(v.addEdge("o2o",u),txid); rs[8]=sign(v.addEdge("o2m",u),txid); rs[4]=sign(v.addEdge("em",u),txid); rs[5]=sign(v.addEdge("emf",u),txid); newTx(); long vid = getId(v), uid = getId(u); JanusGraphTransaction tx1 = graph.newTransaction(); JanusGraphTransaction tx2 = graph.newTransaction(); final int wintx = 20; processTx(tx1,wintx-10,vid,uid); processTx(tx2,wintx,vid,uid); tx1.commit(); Thread.sleep(5); tx2.commit(); //tx2 should win using time-based eventual consistency newTx(); v = getV(tx,vid); assertEquals(6.0,v.<Double>value("weight").doubleValue(),0.00001); VertexProperty p = getOnlyElement(v.properties("weight")); assertEquals(wintx,p.<Integer>value("sig").intValue()); p = getOnlyElement(v.properties("name")); assertEquals("Bob",p.value()); assertEquals(wintx,p.<Integer>value("sig").intValue()); p = getOnlyElement(v.properties("value")); assertEquals(rs[2].longId(),getId(p)); assertEquals(wintx,p.<Integer>value("sig").intValue()); assertCount(2,v.properties("valuef")); for (Iterator<VertexProperty<Object>> ppiter = v.properties("valuef"); ppiter.hasNext(); ) { VertexProperty pp = ppiter.next(); assertNotEquals(rs[3].longId(),getId(pp)); assertEquals(2,pp.value()); } Edge e = Iterables.<JanusGraphEdge>getOnlyElement(v.query().direction(OUT).labels("es").edges()); assertEquals(wintx,e.<Integer>value("sig").intValue()); assertNotEquals(rs[6].longId(),getId(e)); e = Iterables.<JanusGraphEdge>getOnlyElement(v.query().direction(OUT).labels("o2o").edges()); assertEquals(wintx,e.<Integer>value("sig").intValue()); assertEquals(rs[7].longId(), getId(e)); e = Iterables.<JanusGraphEdge>getOnlyElement(v.query().direction(OUT).labels("o2m").edges()); assertEquals(wintx,e.<Integer>value("sig").intValue()); assertNotEquals(rs[8].longId(),getId(e)); e = Iterables.<JanusGraphEdge>getOnlyElement(v.query().direction(OUT).labels("em").edges()); assertEquals(wintx,e.<Integer>value("sig").intValue()); assertEquals(rs[4].longId(), getId(e)); for (Object o : v.query().direction(OUT).labels("emf").edges()) { Edge ee = (Edge) o; assertNotEquals(rs[5].longId(),getId(ee)); assertEquals(uid,ee.inVertex().id()); } } private void processTx(JanusGraphTransaction tx, int txid, long vid, long uid) { JanusGraphVertex v = getV(tx,vid); JanusGraphVertex u = getV(tx,uid); assertEquals(5.0,v.<Double>value("weight").doubleValue(),0.00001); VertexProperty p = getOnlyElement(v.properties("weight")); assertEquals(1,p.<Integer>value("sig").intValue()); sign(v.property("weight",6.0),txid); p = getOnlyElement(v.properties("name")); assertEquals(1,p.<Integer>value("sig").intValue()); assertEquals("John",p.value()); p.remove(); sign(v.property("name","Bob"),txid); for (String pkey : new String[]{"value","valuef"}) { p = getOnlyElement(v.properties(pkey)); assertEquals(1,p.<Integer>value("sig").intValue()); assertEquals(2,p.value()); sign((JanusGraphVertexProperty)p,txid); } Edge e = Iterables.<JanusGraphEdge>getOnlyElement(v.query().direction(OUT).labels("es").edges()); assertEquals(1,e.<Integer>value("sig").intValue()); e.remove(); sign(v.addEdge("es",u),txid); e = Iterables.<JanusGraphEdge>getOnlyElement(v.query().direction(OUT).labels("o2o").edges()); assertEquals(1,e.<Integer>value("sig").intValue()); sign((JanusGraphEdge)e,txid); e = Iterables.<JanusGraphEdge>getOnlyElement(v.query().direction(OUT).labels("o2m").edges()); assertEquals(1,e.<Integer>value("sig").intValue()); e.remove(); sign(v.addEdge("o2m",u),txid); for (String label : new String[]{"em","emf"}) { e = Iterables.<JanusGraphEdge>getOnlyElement(v.query().direction(OUT).labels(label).edges()); assertEquals(1,e.<Integer>value("sig").intValue()); sign((JanusGraphEdge)e,txid); } } private JanusGraphRelation sign(JanusGraphRelation r, int id) { r.property("sig",id); return r; } }