// 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.idmanagement;
import com.google.common.collect.ImmutableList;
import org.janusgraph.diskstorage.ReadBuffer;
import org.janusgraph.diskstorage.StaticBuffer;
import org.janusgraph.diskstorage.WriteBuffer;
import org.janusgraph.diskstorage.keycolumnvalue.KeyRange;
import org.janusgraph.diskstorage.util.BufferUtil;
import org.janusgraph.diskstorage.util.WriteByteBuffer;
import org.janusgraph.graphdb.database.idassigner.placement.PartitionIDRange;
import org.janusgraph.graphdb.database.idhandling.IDHandler;
import org.janusgraph.graphdb.database.idhandling.VariableLong;
import org.janusgraph.graphdb.database.serialize.DataOutput;
import org.janusgraph.graphdb.database.serialize.Serializer;
import org.janusgraph.graphdb.database.serialize.StandardSerializer;
import org.janusgraph.graphdb.internal.RelationCategory;
import org.janusgraph.graphdb.types.system.*;
import org.janusgraph.testutil.RandomGenerator;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import java.util.List;
import java.util.Random;
import static org.junit.Assert.*;
public class IDManagementTest {
private static final Random random = new Random();
private static final IDManager.VertexIDType[] USER_VERTEX_TYPES = {IDManager.VertexIDType.NormalVertex,
IDManager.VertexIDType.PartitionedVertex, IDManager.VertexIDType.UnmodifiableVertex};
@Before
public void setUp() throws Exception {
}
@After
public void tearDown() throws Exception {
}
@Test
public void EntityIDTest() {
testEntityID(12, 2341, 1234123, 1235123);
testEntityID(16, 64000, 582919, 583219);
testEntityID(4, 14, 1, 1000);
testEntityID(10, 1, 903392, 903592);
testEntityID(0, 0, 242342, 249342);
try {
testEntityID(0, 1, 242342, 242345);
fail();
} catch (IllegalArgumentException e) {}
try {
testEntityID(0, 0, -11, -10);
fail();
} catch (IllegalArgumentException e) {}
}
public void testEntityID(int partitionBits, int partition, long minCount, long maxCount) {
IDManager eid = new IDManager(partitionBits);
assertTrue(eid.getPartitionBound()>0);
assertTrue(eid.getPartitionBound()<=1l+Integer.MAX_VALUE);
assertTrue(eid.getRelationCountBound()>0);
assertTrue(IDManager.getSchemaCountBound()>0);
assertTrue(eid.getVertexCountBound()>0);
try {
IDManager.getTemporaryVertexID(IDManager.VertexIDType.RelationType,5);
fail();
} catch (IllegalArgumentException e) {}
for (long count=minCount;count<maxCount;count++) {
for (IDManager.VertexIDType vtype : USER_VERTEX_TYPES) {
if (partitionBits==0 && vtype== IDManager.VertexIDType.PartitionedVertex) continue;
if (vtype== IDManager.VertexIDType.PartitionedVertex) partition=IDManager.PARTITIONED_VERTEX_PARTITION;
long id = eid.getVertexID(count, partition,vtype);
assertTrue(eid.isUserVertexId(id));
assertTrue(vtype.is(id));
if (vtype != IDManager.VertexIDType.PartitionedVertex) assertEquals(eid.getPartitionId(id), partition);
assertEquals(id, eid.getKeyID(eid.getKey(id)));
}
long id = eid.getRelationID(count, partition);
assertTrue(id>=partition);
id = IDManager.getSchemaId(IDManager.VertexIDType.UserPropertyKey, count);
assertTrue(eid.isPropertyKeyId(id));
assertTrue(eid.isRelationTypeId(id));
assertFalse(IDManager.isSystemRelationTypeId(id));
assertEquals(id, eid.getKeyID(eid.getKey(id)));
id = IDManager.getSchemaId(IDManager.VertexIDType.SystemPropertyKey, count);
assertTrue(eid.isPropertyKeyId(id));
assertTrue(eid.isRelationTypeId(id));
assertTrue(IDManager.isSystemRelationTypeId(id));
id = IDManager.getSchemaId(IDManager.VertexIDType.UserEdgeLabel,count);
assertTrue(eid.isEdgeLabelId(id));
assertTrue(eid.isRelationTypeId(id));
assertEquals(id, eid.getKeyID(eid.getKey(id)));
id = IDManager.getTemporaryVertexID(IDManager.VertexIDType.NormalVertex,count);
assertTrue(IDManager.isTemporary(id));
assertTrue(IDManager.VertexIDType.NormalVertex.is(id));
id = IDManager.getTemporaryVertexID(IDManager.VertexIDType.UserEdgeLabel,count);
assertTrue(IDManager.isTemporary(id));
assertTrue(IDManager.VertexIDType.UserEdgeLabel.is(id));
id = IDManager.getTemporaryRelationID(count);
assertTrue(IDManager.isTemporary(id));
id = IDManager.getTemporaryVertexID(IDManager.VertexIDType.InvisibleVertex,count);
assertTrue(IDManager.isTemporary(id));
assertTrue(IDManager.VertexIDType.Invisible.is(id));
}
}
@Test
public void edgeTypeIDTest() {
int partitionBits = 16;
IDManager eid = new IDManager(partitionBits);
int trails = 1000000;
assertEquals(eid.getPartitionBound(), (1l << partitionBits));
Serializer serializer = new StandardSerializer();
for (int t = 0; t < trails; t++) {
long count = RandomGenerator.randomLong(1, IDManager.getSchemaCountBound());
long id;
IDHandler.DirectionID dirID;
RelationCategory type;
if (Math.random() < 0.5) {
id = IDManager.getSchemaId(IDManager.VertexIDType.UserEdgeLabel,count);
assertTrue(eid.isEdgeLabelId(id));
assertFalse(IDManager.isSystemRelationTypeId(id));
type = RelationCategory.EDGE;
if (Math.random() < 0.5)
dirID = IDHandler.DirectionID.EDGE_IN_DIR;
else
dirID = IDHandler.DirectionID.EDGE_OUT_DIR;
} else {
type = RelationCategory.PROPERTY;
id = IDManager.getSchemaId(IDManager.VertexIDType.UserPropertyKey, count);
assertTrue(eid.isPropertyKeyId(id));
assertFalse(IDManager.isSystemRelationTypeId(id));
dirID = IDHandler.DirectionID.PROPERTY_DIR;
}
assertTrue(eid.isRelationTypeId(id));
StaticBuffer b = IDHandler.getRelationType(id, dirID, false);
// System.out.println(dirID);
// System.out.println(getBinary(id));
// System.out.println(getBuffer(b.asReadBuffer()));
ReadBuffer rb = b.asReadBuffer();
IDHandler.RelationTypeParse parse = IDHandler.readRelationType(rb);
assertEquals(id,parse.typeId);
assertEquals(dirID, parse.dirID);
assertFalse(rb.hasRemaining());
//Inline edge type
WriteBuffer wb = new WriteByteBuffer(9);
IDHandler.writeInlineRelationType(wb, id);
long newId = IDHandler.readInlineRelationType(wb.getStaticBuffer().asReadBuffer());
assertEquals(id,newId);
//Compare to Kryo
DataOutput out = serializer.getDataOutput(10);
IDHandler.writeRelationType(out, id, dirID, false);
assertEquals(b, out.getStaticBuffer());
//Make sure the bounds are right
StaticBuffer[] bounds = IDHandler.getBounds(type,false);
assertTrue(bounds[0].compareTo(b)<0);
assertTrue(bounds[1].compareTo(b)>0);
bounds = IDHandler.getBounds(RelationCategory.RELATION,false);
assertTrue(bounds[0].compareTo(b)<0);
assertTrue(bounds[1].compareTo(b)>0);
}
}
private static final SystemRelationType[] SYSTEM_TYPES = {BaseKey.VertexExists, BaseKey.SchemaDefinitionProperty,
BaseLabel.SchemaDefinitionEdge, ImplicitKey.VISIBILITY, ImplicitKey.TIMESTAMP};
@Test
public void writingInlineEdgeTypes() {
int numTries = 100;
WriteBuffer out = new WriteByteBuffer(8*numTries);
for (SystemRelationType t : SYSTEM_TYPES) {
IDHandler.writeInlineRelationType(out, t.longId());
}
for (long i=1;i<=numTries;i++) {
IDHandler.writeInlineRelationType(out, IDManager.getSchemaId(IDManager.VertexIDType.UserEdgeLabel, i * 1000));
}
ReadBuffer in = out.getStaticBuffer().asReadBuffer();
for (SystemRelationType t : SYSTEM_TYPES) {
assertEquals(t, SystemTypeManager.getSystemType(IDHandler.readInlineRelationType(in)));
}
for (long i=1;i<=numTries;i++) {
assertEquals(i * 1000, IDManager.stripEntireRelationTypePadding(IDHandler.readInlineRelationType(in)));
}
}
@Test
public void testDirectionPrefix() {
for (RelationCategory type : RelationCategory.values()) {
for (boolean system : new boolean[]{true,false}) {
StaticBuffer[] bounds = IDHandler.getBounds(type,system);
assertEquals(1,bounds[0].length());
assertEquals(1,bounds[1].length());
assertTrue(bounds[0].compareTo(bounds[1])<0);
assertTrue(bounds[1].compareTo(BufferUtil.oneBuffer(1))<0);
}
}
}
@Test
public void testEdgeTypeWriting() {
for (SystemRelationType t : SYSTEM_TYPES) {
testEdgeTypeWriting(t.longId());
}
for (int i=0;i<1000;i++) {
IDManager.VertexIDType type = random.nextDouble()<0.5? IDManager.VertexIDType.UserPropertyKey: IDManager.VertexIDType.UserEdgeLabel;
testEdgeTypeWriting(IDManager.getSchemaId(type,random.nextInt(1000000000)));
}
}
public void testEdgeTypeWriting(long etid) {
IDHandler.DirectionID[] dir = IDManager.VertexIDType.EdgeLabel.is(etid)?
new IDHandler.DirectionID[]{IDHandler.DirectionID.EDGE_IN_DIR, IDHandler.DirectionID.EDGE_OUT_DIR}:
new IDHandler.DirectionID[]{IDHandler.DirectionID.PROPERTY_DIR};
boolean invisible = IDManager.isSystemRelationTypeId(etid);
for (IDHandler.DirectionID d : dir) {
StaticBuffer b = IDHandler.getRelationType(etid, d, invisible);
IDHandler.RelationTypeParse parse = IDHandler.readRelationType(b.asReadBuffer());
assertEquals(d,parse.dirID);
assertEquals(etid,parse.typeId);
}
}
@Test
public void testUserVertexBitWdith() {
for (IDManager.VertexIDType type : IDManager.VertexIDType.values()) {
if (IDManager.VertexIDType.UserVertex.is(type.suffix()) && type.isProper())
assert type.offset()==IDManager.USERVERTEX_PADDING_BITWIDTH;
assertTrue(type.offset()<=IDManager.MAX_PADDING_BITWIDTH);
}
}
@Test
public void partitionIDRangeTest() {
List<PartitionIDRange> result = PartitionIDRange.getIDRanges(16, ImmutableList.of(getKeyRange(120<<16, 6, 140<<16, 8)));
assertTrue(result.size()==1);
PartitionIDRange r = result.get(0);
assertEquals(121,r.getLowerID());
assertEquals(140,r.getUpperID());
assertEquals(1<<16,r.getIdUpperBound());
result = PartitionIDRange.getIDRanges(16, ImmutableList.of(getKeyRange(120<<16, 0, 140<<16, 0)));
assertTrue(result.size()==1);
r = result.get(0);
assertEquals(120,r.getLowerID());
assertEquals(140,r.getUpperID());
result = PartitionIDRange.getIDRanges(8, ImmutableList.of(getKeyRange(250<<24, 0, 0<<24, 0)));
assertTrue(result.size()==1);
r = result.get(0);
assertEquals(250,r.getLowerID());
assertEquals(0,r.getUpperID());
for (int i=0;i<255;i=i+5) {
result = PartitionIDRange.getIDRanges(8, ImmutableList.of(getKeyRange(i<<24, 0, i<<24, 0)));
assertTrue(result.size()==1);
r = result.get(0);
for (int j=0;j<255;j++) assertTrue(r.contains(j));
}
result = PartitionIDRange.getIDRanges(8, ImmutableList.of(getKeyRange(1<<24, 0, 1<<24, 1)));
assertTrue(result.isEmpty());
result = PartitionIDRange.getIDRanges(8, ImmutableList.of(getKeyRange(1<<28, 6, 1<<28, 8)));
assertTrue(result.isEmpty());
result = PartitionIDRange.getIDRanges(8, ImmutableList.of(getKeyRange(33<<24, 6, 34<<24, 8)));
assertTrue(result.isEmpty());
}
private static KeyRange getKeyRange(int s1, long l1, int s2, long l2) {
return new KeyRange(getBufferOf(s1,l1),getBufferOf(s2,l2));
}
private static StaticBuffer getBufferOf(int s, long l) {
WriteBuffer out = new WriteByteBuffer(4+8);
out.putInt(s);
out.putLong(l);
return out.getStaticBuffer();
}
public String getBuffer(ReadBuffer r) {
String result = "";
while (r.hasRemaining()) {
result += getBinary(VariableLong.unsignedByte(r.getByte()),8) + " ";
}
return result;
}
public String getBinary(long id) {
return getBinary(id,64);
}
public String getBinary(long id, int normalizedLength) {
String s = Long.toBinaryString(id);
while (s.length() < normalizedLength) {
s = "0" + s;
}
return s;
}
}