/* * Copyright (C) 2020 Grakn Labs * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as * published by the Free Software Foundation, either version 3 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see <https://www.gnu.org/licenses/>. */ package grakn.core.graph.graphdb.database.idhandling; import grakn.core.graph.diskstorage.ReadBuffer; import grakn.core.graph.diskstorage.StaticBuffer; import grakn.core.graph.diskstorage.WriteBuffer; import grakn.core.graph.diskstorage.util.BufferUtil; import grakn.core.graph.diskstorage.util.StaticArrayBuffer; import grakn.core.graph.diskstorage.util.WriteByteBuffer; import grakn.core.graph.graphdb.idmanagement.IDManager; import grakn.core.graph.graphdb.internal.RelationCategory; import org.apache.tinkerpop.gremlin.structure.Direction; import static grakn.core.graph.graphdb.idmanagement.IDManager.VertexIDType.SystemEdgeLabel; import static grakn.core.graph.graphdb.idmanagement.IDManager.VertexIDType.SystemPropertyKey; import static grakn.core.graph.graphdb.idmanagement.IDManager.VertexIDType.UserEdgeLabel; import static grakn.core.graph.graphdb.idmanagement.IDManager.VertexIDType.UserPropertyKey; public class IDHandler { public static final StaticBuffer MIN_KEY = BufferUtil.getLongBuffer(0); public static final StaticBuffer MAX_KEY = BufferUtil.getLongBuffer(-1); public enum DirectionID { PROPERTY_DIR(0), //00b EDGE_OUT_DIR(2), //10b EDGE_IN_DIR(3); //11b private final int id; DirectionID(int id) { this.id = id; } private int getRelationType() { return id >>> 1; } private int getDirectionInt() { return id & 1; } public RelationCategory getRelationCategory() { switch (this) { case PROPERTY_DIR: return RelationCategory.PROPERTY; case EDGE_IN_DIR: case EDGE_OUT_DIR: return RelationCategory.EDGE; default: throw new AssertionError(); } } public Direction getDirection() { switch (this) { case PROPERTY_DIR: case EDGE_OUT_DIR: return Direction.OUT; case EDGE_IN_DIR: return Direction.IN; default: throw new AssertionError(); } } private int getPrefix(boolean invisible, boolean systemType) { return ((systemType ? 0 : invisible ? 2 : 1) << 1) + getRelationType(); } private static DirectionID getDirectionID(int relationType, int direction) { return forId((relationType << 1) + direction); } private static DirectionID forId(int id) { switch (id) { case 0: return PROPERTY_DIR; case 2: return EDGE_OUT_DIR; case 3: return EDGE_IN_DIR; default: throw new AssertionError("Invalid id: " + id); } } } private static final int PREFIX_BIT_LEN = 3; private static int relationTypeLength(long relationTypeId) { return VariableLong.positiveWithPrefixLength(IDManager.stripEntireRelationTypePadding(relationTypeId) << 1, PREFIX_BIT_LEN); } /** * The edge type is written as follows: [ Invisible & System (2 bit) | Relation-Type-ID (1 bit) | Relation-Type-Count (variable) | Direction-ID (1 bit)] * Would only need 1 bit to store relation-type-id, but using two so we can upper bound. */ public static void writeRelationType(WriteBuffer out, long relationTypeId, DirectionID dirID, boolean invisible) { long strippedId = (IDManager.stripEntireRelationTypePadding(relationTypeId) << 1) + dirID.getDirectionInt(); VariableLong.writePositiveWithPrefix(out, strippedId, dirID.getPrefix(invisible, IDManager.isSystemRelationTypeId(relationTypeId)), PREFIX_BIT_LEN); } public static StaticBuffer getRelationType(long relationTypeId, DirectionID dirID, boolean invisible) { WriteBuffer b = new WriteByteBuffer(relationTypeLength(relationTypeId)); IDHandler.writeRelationType(b, relationTypeId, dirID, invisible); return b.getStaticBuffer(); } public static RelationTypeParse readRelationType(ReadBuffer in) { long[] countPrefix = VariableLong.readPositiveWithPrefix(in, PREFIX_BIT_LEN); DirectionID dirID = DirectionID.getDirectionID((int) countPrefix[1] & 1, (int) (countPrefix[0] & 1)); long typeId = countPrefix[0] >>> 1; boolean isSystemType = (countPrefix[1] >> 1) == 0; if (dirID == DirectionID.PROPERTY_DIR) { typeId = IDManager.getSchemaId(isSystemType ? SystemPropertyKey : UserPropertyKey, typeId); } else { typeId = IDManager.getSchemaId(isSystemType ? SystemEdgeLabel : UserEdgeLabel, typeId); } return new RelationTypeParse(typeId, dirID); } public static class RelationTypeParse { public final long typeId; public final DirectionID dirID; RelationTypeParse(long typeId, DirectionID dirID) { this.typeId = typeId; this.dirID = dirID; } } public static void writeInlineRelationType(WriteBuffer out, long relationTypeId) { long compressId = IDManager.stripRelationTypePadding(relationTypeId); VariableLong.writePositive(out, compressId); } public static long readInlineRelationType(ReadBuffer in) { long compressId = VariableLong.readPositive(in); return IDManager.addRelationTypePadding(compressId); } private static StaticBuffer getPrefixed(int prefix) { byte[] arr = new byte[1]; arr[0] = (byte) (prefix << (Byte.SIZE - PREFIX_BIT_LEN)); return new StaticArrayBuffer(arr); } public static StaticBuffer[] getBounds(RelationCategory type, boolean systemTypes) { int start, end; switch (type) { case PROPERTY: start = DirectionID.PROPERTY_DIR.getPrefix(systemTypes, systemTypes); end = start; break; case EDGE: start = DirectionID.EDGE_OUT_DIR.getPrefix(systemTypes, systemTypes); end = start; break; case RELATION: start = DirectionID.PROPERTY_DIR.getPrefix(systemTypes, systemTypes); end = DirectionID.EDGE_OUT_DIR.getPrefix(systemTypes, systemTypes); break; default: throw new AssertionError("Unrecognized type:" + type); } end++; return new StaticBuffer[]{getPrefixed(start), getPrefixed(end)}; } }