diff --git a/src/main/java/org/apache/tinkerpop/gremlin/tinkergraph/storage/Deserializer.java b/src/main/java/org/apache/tinkerpop/gremlin/tinkergraph/storage/Deserializer.java index ae72348..7903a61 100644 --- a/src/main/java/org/apache/tinkerpop/gremlin/tinkergraph/storage/Deserializer.java +++ b/src/main/java/org/apache/tinkerpop/gremlin/tinkergraph/storage/Deserializer.java @@ -25,6 +25,7 @@ import org.msgpack.core.MessageUnpacker; import org.msgpack.value.ArrayValue; import org.msgpack.value.Value; +import org.msgpack.value.ValueType; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -33,6 +34,7 @@ import java.util.Iterator; import java.util.List; import java.util.Map; +import java.util.Optional; public abstract class Deserializer { private final Logger logger = LoggerFactory.getLogger(getClass()); @@ -41,7 +43,9 @@ public abstract class Deserializer { protected abstract boolean elementRefRequiresAdjacentElements(); - /** edgeId maps are passed dependent on `elementRefRequiresAdjacentElements`*/ + /** + * edgeId maps are passed dependent on `elementRefRequiresAdjacentElements` + */ protected abstract ElementRef createElementRef(long id, String label, Map inEdgeIdsByLabel, @@ -49,11 +53,15 @@ protected abstract ElementRef createElementRef(long id, protected abstract A createElement(long id, String label, - Map properties, + Optional> properties, Map inEdgeIdsByLabel, Map outEdgeIdsByLabel); - public A deserialize(byte[] bytes) throws IOException { + // TODO speedup by restructuring or memoizing the results + protected abstract Map propertyTypeByIndex(String label); + protected abstract Map propertyNamesByIndex(String label); + + public A deserialize(final byte[] bytes, final boolean readProperties) throws IOException { long start = System.currentTimeMillis(); if (null == bytes) return null; @@ -63,7 +71,14 @@ public A deserialize(byte[] bytes) throws IOException { final String label = unpacker.unpackString(); final Map inEdgeIdsByLabel = unpackEdgeIdsByLabel(unpacker); final Map outEdgeIdsByLabel = unpackEdgeIdsByLabel(unpacker); - final Map properties = unpackProperties(unpacker); + + final Optional> properties; + if (readProperties) { + final Map valuesByPropertyIndex = unpackAllProperties(unpacker, propertyTypeByIndex(label)); + properties = Optional.of(convertPropertyIndexToPropertyNames(valuesByPropertyIndex, propertyNamesByIndex(label))); + } else { + properties = Optional.empty(); + } A a = createElement(id, label, properties, inEdgeIdsByLabel, outEdgeIdsByLabel); @@ -80,7 +95,7 @@ public A deserialize(byte[] bytes) throws IOException { /** * only deserialize the part we're keeping in memory, used during startup when initializing from disk */ - public ElementRef deserializeRef(byte[] bytes) throws IOException { + public ElementRef deserializeRef(final byte[] bytes) throws IOException { try (MessageUnpacker unpacker = MessagePack.newDefaultUnpacker(bytes)) { long id = unpacker.unpackLong(); String label = unpacker.unpackString(); @@ -95,49 +110,73 @@ public ElementRef deserializeRef(byte[] bytes) throws IOException { } } - private Map unpackProperties(MessageUnpacker unpacker) throws IOException { - int propertyCount = unpacker.unpackMapHeader(); - Map res = new THashMap<>(propertyCount); - for (int i = 0; i < propertyCount; i++) { - final String key = unpacker.unpackString(); - final Object unpackedProperty = unpackProperty(unpacker.unpackValue().asArrayValue()); - res.put(key, unpackedProperty); + /** + * @return propertyValue by propertyIndex. properties are complete, but (therefor) values may be `null` + */ + private Map unpackAllProperties(final MessageUnpacker unpacker, final Map propertyTypeByIndex) throws IOException { + int propertyCount = unpacker.unpackArrayHeader(); + Map res = new THashMap<>(propertyCount); + for (int idx = 0; idx < propertyCount; idx++) { + final Class propertyType = propertyTypeByIndex.get(idx); + res.put(idx, unpackProperty(unpacker.unpackValue(), propertyType)); } return res; } - private Object unpackProperty(final ArrayValue packedValueAndType) { - final Iterator iter = packedValueAndType.iterator(); - final byte valueTypeId = iter.next().asIntegerValue().asByte(); - final Value value = iter.next(); - - switch (ValueTypes.lookup(valueTypeId)) { - case BOOLEAN: - return value.asBooleanValue().getBoolean(); - case STRING: - return value.asStringValue().asString(); - case BYTE: - return value.asIntegerValue().asByte(); - case SHORT: - return value.asIntegerValue().asShort(); - case INTEGER: - return value.asIntegerValue().asInt(); - case LONG: - return value.asIntegerValue().asLong(); - case FLOAT: - return value.asFloatValue().toFloat(); - case DOUBLE: - return Double.valueOf(value.asFloatValue().toFloat()); - case LIST: - final ArrayValue arrayValue = value.asArrayValue(); - List deserializedArray = new ArrayList(arrayValue.size()); - final Iterator valueIterator = arrayValue.iterator(); - while (valueIterator.hasNext()) { - deserializedArray.add(unpackProperty(valueIterator.next().asArrayValue())); - } - return deserializedArray; - default: - throw new NotImplementedException("unknown valueTypeId=`" + valueTypeId); + /** + * only deserialize one specific property, identified by it's index + */ + public Object unpackSpecificProperty(byte[] bytes, int propertyIdx, final Class propertyType) throws IOException { + try (MessageUnpacker unpacker = MessagePack.newDefaultUnpacker(bytes)) { + // skip over values we don't care about + unpacker.skipValue(2); // id and label + unpacker.skipValue(2); // [in|out]EdgeIdsByLabel maps + unpacker.unpackArrayHeader(); // skip over array header (for property count) + unpacker.skipValue(propertyIdx); // skip to required property + + return unpackProperty(unpacker.unpackValue(), propertyType); + } + } + + /** + * `nil` in the binary is mapped to `null` + */ + private Object unpackProperty(final Value value, final Class propertyType) { + final ValueType valueType = value.getValueType(); + if (value.isNilValue()) { + return null; + } else if (value.isArrayValue()) { + final ArrayValue arrayValue = value.asArrayValue(); + List deserializedArray = new ArrayList(arrayValue.size()); + final Iterator valueIterator = arrayValue.iterator(); + while (valueIterator.hasNext()) deserializedArray.add(unpackProperty(valueIterator.next(), propertyType)); + return deserializedArray; + } else if (propertyType.equals(Boolean.class)) { + if (!valueType.isBooleanType()) throw new UnexpectedPropertyTypeException(propertyType, value); + return value.asBooleanValue().getBoolean(); + } else if (propertyType.equals(String.class)) { + if (!valueType.isStringType()) throw new UnexpectedPropertyTypeException(propertyType, value); + return value.asStringValue().asString(); + } else if (propertyType.equals(Byte.class)) { + if (!valueType.isIntegerType()) throw new UnexpectedPropertyTypeException(propertyType, value); + return value.asIntegerValue().asByte(); + } else if (propertyType.equals(Short.class)) { + if (!valueType.isIntegerType()) throw new UnexpectedPropertyTypeException(propertyType, value); + return value.asIntegerValue().asShort(); + } else if (propertyType.equals(Integer.class)) { + if (!valueType.isIntegerType()) throw new UnexpectedPropertyTypeException(propertyType, value); + return value.asIntegerValue().asInt(); + } else if (propertyType.equals(Long.class)) { + if (!valueType.isIntegerType()) throw new UnexpectedPropertyTypeException(propertyType, value); + return value.asIntegerValue().asLong(); + } else if (propertyType.equals(Float.class)) { + if (!valueType.isFloatType()) throw new UnexpectedPropertyTypeException(propertyType, value); + return value.asFloatValue().toFloat(); + } else if (propertyType.equals(Double.class)) { + if (!valueType.isFloatType()) throw new UnexpectedPropertyTypeException(propertyType, value); + return Double.valueOf(value.asFloatValue().toFloat()); + } else { + throw new NotImplementedException("unknown propertyType=`" + propertyType + " for value=" + value); } } @@ -164,20 +203,27 @@ protected Object[] toTinkerpopKeyValues(Map properties) { for (Map.Entry entry : properties.entrySet()) { final String key = entry.getKey(); final Object property = entry.getValue(); - // special handling for lists: create separate key/value entry for each list entry - if (property instanceof List) { - for (Object value : (List) property) { + if (property != null) { + // special handling for lists: create separate key/value entry for each list entry + if (property instanceof List) { + for (Object value : (List) property) { + keyValues.add(key); + keyValues.add(value); + } + } else { keyValues.add(key); - keyValues.add(value); + keyValues.add(property); } - } else { - keyValues.add(key); - keyValues.add(property); } } return keyValues.toArray(); } + private Map convertPropertyIndexToPropertyNames(Map valuesByPropertyIndex, Map propertyNamesByIndex) { + Map valuesByPropertyName = new THashMap<>(valuesByPropertyIndex.size()); + valuesByPropertyIndex.forEach((index, value) -> valuesByPropertyName.put(propertyNamesByIndex.get(index), value)); + return valuesByPropertyName; + } } diff --git a/src/main/java/org/apache/tinkerpop/gremlin/tinkergraph/storage/EdgeDeserializer.java b/src/main/java/org/apache/tinkerpop/gremlin/tinkergraph/storage/EdgeDeserializer.java index 6e20266..110c44b 100644 --- a/src/main/java/org/apache/tinkerpop/gremlin/tinkergraph/storage/EdgeDeserializer.java +++ b/src/main/java/org/apache/tinkerpop/gremlin/tinkergraph/storage/EdgeDeserializer.java @@ -28,6 +28,7 @@ import org.apache.tinkerpop.gremlin.tinkergraph.structure.VertexRef; import java.util.Map; +import java.util.Optional; public class EdgeDeserializer extends Deserializer { protected final TinkerGraph graph; @@ -51,16 +52,25 @@ protected ElementRef createElementRef(long id, String label, Map } @Override - protected Edge createElement(long id, String label, Map properties, Map inVertexIdsByLabel, Map outVertexIdsByLabel) { + protected Edge createElement(long id, String label, Optional> properties, Map inVertexIdsByLabel, Map outVertexIdsByLabel) { VertexRef outVertexRef = getVertexRef(outVertexIdsByLabel, Direction.OUT); VertexRef inVertexRef = getVertexRef(inVertexIdsByLabel, Direction.IN); SpecializedTinkerEdge edge = edgeFactoryByLabel.get(label).createEdge(id, graph, outVertexRef, inVertexRef); - ElementHelper.attachProperties(edge, toTinkerpopKeyValues(properties)); - + properties.ifPresent(props -> ElementHelper.attachProperties(edge, toTinkerpopKeyValues(props))); edge.setModifiedSinceLastSerialization(false); return edge; } + @Override + protected Map propertyTypeByIndex(String label) { + return edgeFactoryByLabel.get(label).propertyTypeByIndex(); + } + + @Override + protected Map propertyNamesByIndex(String label) { + return edgeFactoryByLabel.get(label).propertyNamesByIndex(); + } + private VertexRef getVertexRef(Map vertexIdsByLabel, Direction direction) { final long[] vertexIds = vertexIdsByLabel.get(direction.name()); assert vertexIds != null; diff --git a/src/main/java/org/apache/tinkerpop/gremlin/tinkergraph/storage/EdgeSerializer.java b/src/main/java/org/apache/tinkerpop/gremlin/tinkergraph/storage/EdgeSerializer.java index 6e402c1..8ee6e42 100644 --- a/src/main/java/org/apache/tinkerpop/gremlin/tinkergraph/storage/EdgeSerializer.java +++ b/src/main/java/org/apache/tinkerpop/gremlin/tinkergraph/storage/EdgeSerializer.java @@ -24,9 +24,13 @@ import org.apache.commons.lang.NotImplementedException; import org.apache.tinkerpop.gremlin.structure.Direction; import org.apache.tinkerpop.gremlin.structure.Edge; +import org.apache.tinkerpop.gremlin.tinkergraph.structure.ElementRef; +import org.apache.tinkerpop.gremlin.tinkergraph.structure.SpecializedTinkerEdge; +import org.apache.tinkerpop.gremlin.tinkergraph.structure.TinkerEdge; import java.util.Arrays; import java.util.Map; +import java.util.SortedMap; public class EdgeSerializer extends Serializer { @@ -41,10 +45,15 @@ protected String getLabel(Edge edge) { } @Override - protected Map getProperties(Edge edge) { - Map properties = new THashMap<>(); - edge.properties().forEachRemaining(property -> properties.put(property.key(), property.value())); - return properties; + protected SortedMap getProperties(Edge edge) { + if (edge instanceof ElementRef) { + edge = ((ElementRef) edge).get(); + } + if (edge instanceof SpecializedTinkerEdge) { + return ((SpecializedTinkerEdge) edge).propertiesByStorageIdx(); + } else { + throw new org.apache.commons.lang3.NotImplementedException("EdgeSerializer.getProperties for generic edges"); + } } @Override diff --git a/src/main/java/org/apache/tinkerpop/gremlin/tinkergraph/storage/OndiskOverflow.java b/src/main/java/org/apache/tinkerpop/gremlin/tinkergraph/storage/OndiskOverflow.java index 98dab94..b206c8a 100644 --- a/src/main/java/org/apache/tinkerpop/gremlin/tinkergraph/storage/OndiskOverflow.java +++ b/src/main/java/org/apache/tinkerpop/gremlin/tinkergraph/storage/OndiskOverflow.java @@ -101,12 +101,12 @@ public void persist(final TinkerElement element) throws IOException { } } - public A readVertex(final long id) throws IOException { - return (A) vertexDeserializer.get().deserialize(vertexMVMap.get(id)); + public A readVertex(final long id, final boolean readProperties) throws IOException { + return (A) vertexDeserializer.get().deserialize(vertexMVMap.get(id), readProperties); } - public A readEdge(final long id) throws IOException { - return (A) edgeDeserializer.get().deserialize(edgeMVMap.get(id)); + public A readEdge(final long id, final boolean readProperties) throws IOException { + return (A) edgeDeserializer.get().deserialize(edgeMVMap.get(id), readProperties); } @Override diff --git a/src/main/java/org/apache/tinkerpop/gremlin/tinkergraph/storage/Serializer.java b/src/main/java/org/apache/tinkerpop/gremlin/tinkergraph/storage/Serializer.java index 96c3d71..b2386dd 100644 --- a/src/main/java/org/apache/tinkerpop/gremlin/tinkergraph/storage/Serializer.java +++ b/src/main/java/org/apache/tinkerpop/gremlin/tinkergraph/storage/Serializer.java @@ -31,6 +31,7 @@ import java.util.Iterator; import java.util.List; import java.util.Map; +import java.util.SortedMap; public abstract class Serializer { private final Logger logger = LoggerFactory.getLogger(getClass()); @@ -39,7 +40,12 @@ public abstract class Serializer { protected abstract long getId(A a); protected abstract String getLabel(A a); - protected abstract Map getProperties(A a); + + /** + * Map, sorted by it's index so we can write it efficiently + */ + protected abstract SortedMap getProperties(A a); + protected abstract Map getEdgeIds(A a, Direction direction); public byte[] serialize(A a) throws IOException { @@ -63,48 +69,46 @@ public byte[] serialize(A a) throws IOException { } /** - * when deserializing, msgpack can't differentiate between e.g. int and long, so we need to encode the type as well - doing that with an array - * i.e. format is: Map[PropertyName, Array(TypeId, PropertyValue)] + * packing as Array[PropertyValue] - each property is identified by it's index */ - private void packProperties(MessageBufferPacker packer, Map properties) throws IOException { - packer.packMapHeader(properties.size()); - for (Map.Entry property : properties.entrySet()) { - packer.packString(property.getKey()); + private void packProperties(MessageBufferPacker packer, SortedMap properties) throws IOException { + packer.packArrayHeader(properties.size()); + int currentIdx = 0; + for (Map.Entry property : properties.entrySet()) { + // to ensure we write the properties with correct index, fill the void with Nil values + final Integer propertyIdx = property.getKey(); + while (propertyIdx > currentIdx) { + packer.packNil(); + currentIdx++; + } + packPropertyValue(packer, property.getValue()); + currentIdx++; } } /** - * format: `[ValueType.id, value]` + * writing just the value itself + * every element type hard-codes the index and type for each property, so it can be deserialized again */ private void packPropertyValue(final MessageBufferPacker packer, final Object value) throws IOException { - packer.packArrayHeader(2); if (value instanceof Boolean) { - packer.packByte(ValueTypes.BOOLEAN.id); packer.packBoolean((Boolean) value); } else if (value instanceof String) { - packer.packByte(ValueTypes.STRING.id); packer.packString((String) value); } else if (value instanceof Byte) { - packer.packByte(ValueTypes.BYTE.id); packer.packByte((byte) value); } else if (value instanceof Short) { - packer.packByte(ValueTypes.SHORT.id); packer.packShort((short) value); } else if (value instanceof Integer) { - packer.packByte(ValueTypes.INTEGER.id); packer.packInt((int) value); } else if (value instanceof Long) { - packer.packByte(ValueTypes.LONG.id); packer.packLong((long) value); } else if (value instanceof Float) { - packer.packByte(ValueTypes.FLOAT.id); packer.packFloat((float) value); } else if (value instanceof Double) { - packer.packByte(ValueTypes.DOUBLE.id); packer.packFloat((float) value); //msgpack doesn't support double, but we still want to deserialize it as a double later } else if (value instanceof List) { - packer.packByte(ValueTypes.LIST.id); List listValue = (List) value; packer.packArrayHeader(listValue.size()); final Iterator listIter = listValue.iterator(); diff --git a/src/main/java/org/apache/tinkerpop/gremlin/tinkergraph/storage/UnexpectedPropertyTypeException.java b/src/main/java/org/apache/tinkerpop/gremlin/tinkergraph/storage/UnexpectedPropertyTypeException.java new file mode 100644 index 0000000..da9dbc2 --- /dev/null +++ b/src/main/java/org/apache/tinkerpop/gremlin/tinkergraph/storage/UnexpectedPropertyTypeException.java @@ -0,0 +1,27 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.tinkerpop.gremlin.tinkergraph.storage; + +import org.msgpack.value.Value; + +public class UnexpectedPropertyTypeException extends RuntimeException { + public UnexpectedPropertyTypeException(Class expected, Value value) { + super("expected " + expected + ", but found a " + value.getValueType() + " (value=" + value + ")"); + } +} diff --git a/src/main/java/org/apache/tinkerpop/gremlin/tinkergraph/storage/ValueTypes.java b/src/main/java/org/apache/tinkerpop/gremlin/tinkergraph/storage/ValueTypes.java deleted file mode 100644 index 293ef4d..0000000 --- a/src/main/java/org/apache/tinkerpop/gremlin/tinkergraph/storage/ValueTypes.java +++ /dev/null @@ -1,55 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance - * with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ -package org.apache.tinkerpop.gremlin.tinkergraph.storage; - -/* when serializing properties we need to encode the id type in a separate entry, to ensure we can deserialize it - * back to the very same type. I would have hoped that MsgPack does that for us, but that's only partly the case. - * E.g. the different integer types cannot be distinguished other than by their value. When we deserialize `42`, - * we have no idea whether it should be deserialized as a byte, short, integer or double */ -public enum ValueTypes { - BOOLEAN((byte) 0), - STRING((byte) 1), - BYTE((byte) 2), - SHORT((byte) 3), - INTEGER((byte) 4), - LONG((byte) 5), - FLOAT((byte) 6), - DOUBLE((byte) 7), - LIST((byte) 8); - - public final byte id; - ValueTypes(byte id) { - this.id = id; - } - - public static ValueTypes lookup(byte id) { - switch (id) { - case 0: return BOOLEAN; - case 1: return STRING; - case 2: return BYTE; - case 3: return SHORT; - case 4: return INTEGER; - case 5: return LONG; - case 6: return FLOAT; - case 7: return DOUBLE; - case 8: return LIST; - default: throw new IllegalArgumentException("unknown id type " + id); - } - } -} diff --git a/src/main/java/org/apache/tinkerpop/gremlin/tinkergraph/storage/VertexDeserializer.java b/src/main/java/org/apache/tinkerpop/gremlin/tinkergraph/storage/VertexDeserializer.java index c9d56b9..f8564ba 100644 --- a/src/main/java/org/apache/tinkerpop/gremlin/tinkergraph/storage/VertexDeserializer.java +++ b/src/main/java/org/apache/tinkerpop/gremlin/tinkergraph/storage/VertexDeserializer.java @@ -25,7 +25,9 @@ import org.apache.tinkerpop.gremlin.tinkergraph.structure.SpecializedElementFactory; import org.apache.tinkerpop.gremlin.tinkergraph.structure.SpecializedTinkerVertex; import org.apache.tinkerpop.gremlin.tinkergraph.structure.TinkerGraph; + import java.util.Map; +import java.util.Optional; public class VertexDeserializer extends Deserializer { protected final TinkerGraph graph; @@ -52,13 +54,13 @@ protected ElementRef createElementRef(long id, String label, Map } @Override - protected Vertex createElement(long id, String label, Map properties, Map inEdgeIdsByLabel, Map outEdgeIdsByLabel) { + protected Vertex createElement(long id, String label, Optional> properties, Map inEdgeIdsByLabel, Map outEdgeIdsByLabel) { SpecializedElementFactory.ForVertex vertexFactory = vertexFactoryByLabel.get(label); if (vertexFactory == null) { throw new AssertionError("vertexFactory not found for label=" + label); } SpecializedTinkerVertex vertex = vertexFactory.createVertex(id, graph); - ElementHelper.attachProperties(vertex, VertexProperty.Cardinality.list, toTinkerpopKeyValues(properties)); + properties.ifPresent(props -> ElementHelper.attachProperties(vertex, VertexProperty.Cardinality.list, toTinkerpopKeyValues(props))); inEdgeIdsByLabel.entrySet().stream().forEach(entry -> { for (long edgeId : entry.getValue()) { @@ -77,4 +79,14 @@ protected Vertex createElement(long id, String label, Map proper return vertex; } + @Override + protected Map propertyTypeByIndex(String label) { + return vertexFactoryByLabel.get(label).propertyTypeByIndex(); + } + + @Override + protected Map propertyNamesByIndex(String label) { + return vertexFactoryByLabel.get(label).propertyNamesByIndex(); + } + } diff --git a/src/main/java/org/apache/tinkerpop/gremlin/tinkergraph/storage/VertexSerializer.java b/src/main/java/org/apache/tinkerpop/gremlin/tinkergraph/storage/VertexSerializer.java index e808252..ce04cf5 100644 --- a/src/main/java/org/apache/tinkerpop/gremlin/tinkergraph/storage/VertexSerializer.java +++ b/src/main/java/org/apache/tinkerpop/gremlin/tinkergraph/storage/VertexSerializer.java @@ -21,11 +21,16 @@ import gnu.trove.map.hash.THashMap; import gnu.trove.set.TLongSet; import gnu.trove.set.hash.TLongHashSet; +import org.apache.commons.lang3.NotImplementedException; import org.apache.tinkerpop.gremlin.structure.Direction; import org.apache.tinkerpop.gremlin.structure.Vertex; +import org.apache.tinkerpop.gremlin.tinkergraph.structure.ElementRef; import org.apache.tinkerpop.gremlin.tinkergraph.structure.SpecializedTinkerVertex; +import org.apache.tinkerpop.gremlin.tinkergraph.structure.TinkerVertex; +import org.apache.tinkerpop.gremlin.tinkergraph.structure.VertexRef; import java.util.Map; +import java.util.SortedMap; public class VertexSerializer extends Serializer { @@ -39,14 +44,18 @@ protected String getLabel(Vertex vertex) { return vertex.label(); } + /** + * Map, sorted by it's index so we can write it efficiently + */ @Override - protected Map getProperties(Vertex vertex) { + protected SortedMap getProperties(Vertex vertex) { + if (vertex instanceof ElementRef) { + vertex = ((ElementRef) vertex).get(); + } if (vertex instanceof SpecializedTinkerVertex) { - return ((SpecializedTinkerVertex) vertex).valueMap(); + return ((SpecializedTinkerVertex) vertex).propertiesByStorageIdx(); } else { - Map properties = new THashMap<>(); - vertex.properties().forEachRemaining(property -> properties.put(property.key(), property.value())); - return properties; + throw new NotImplementedException("VertexSerializer.getProperties for generic vertices"); } } diff --git a/src/main/java/org/apache/tinkerpop/gremlin/tinkergraph/structure/EdgeRef.java b/src/main/java/org/apache/tinkerpop/gremlin/tinkergraph/structure/EdgeRef.java index 04d13db..68eb09f 100644 --- a/src/main/java/org/apache/tinkerpop/gremlin/tinkergraph/structure/EdgeRef.java +++ b/src/main/java/org/apache/tinkerpop/gremlin/tinkergraph/structure/EdgeRef.java @@ -36,8 +36,8 @@ public EdgeRef(final long edgeId, final String label, final TinkerGraph graph) { } @Override - protected E readFromDisk(final long edgeId) throws IOException { - return graph.ondiskOverflow.readEdge(edgeId); + protected E readFromDisk(final long edgeId, final boolean readProperties) throws IOException { + return graph.ondiskOverflow.readEdge(edgeId, readProperties); } @Override diff --git a/src/main/java/org/apache/tinkerpop/gremlin/tinkergraph/structure/ElementRef.java b/src/main/java/org/apache/tinkerpop/gremlin/tinkergraph/structure/ElementRef.java index d87a3e4..bd4804e 100644 --- a/src/main/java/org/apache/tinkerpop/gremlin/tinkergraph/structure/ElementRef.java +++ b/src/main/java/org/apache/tinkerpop/gremlin/tinkergraph/structure/ElementRef.java @@ -20,6 +20,8 @@ import java.io.IOException; import java.lang.ref.SoftReference; +import java.util.Map; + import org.apache.tinkerpop.gremlin.structure.Element; import org.apache.tinkerpop.gremlin.structure.Graph; @@ -76,12 +78,17 @@ protected void clear() throws IOException { } public E get() { + final boolean readProperties = true; + return get(readProperties); + } + + public E get(final boolean readProperties) { E ref = reference; if (ref != null) { return ref; } else { try { - final E element = readFromDisk(id); + final E element = readFromDisk(id, readProperties); if (element == null) throw new IllegalStateException("unable to read element from disk; id=" + id); this.reference = element; this.lastDeserializedTime = System.currentTimeMillis(); @@ -101,7 +108,7 @@ public long getLastDeserializedTime() { return lastDeserializedTime; } - protected abstract E readFromDisk(long elementId) throws IOException; + protected abstract E readFromDisk(final long elementId, final boolean readProperties) throws IOException; @Override public Object id() { diff --git a/src/main/java/org/apache/tinkerpop/gremlin/tinkergraph/structure/MandatoryPropertyUndefinedException.java b/src/main/java/org/apache/tinkerpop/gremlin/tinkergraph/structure/MandatoryPropertyUndefinedException.java new file mode 100644 index 0000000..bd1880a --- /dev/null +++ b/src/main/java/org/apache/tinkerpop/gremlin/tinkergraph/structure/MandatoryPropertyUndefinedException.java @@ -0,0 +1,25 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.tinkerpop.gremlin.tinkergraph.structure; + +public class MandatoryPropertyUndefinedException extends RuntimeException { + public MandatoryPropertyUndefinedException(long id, String label, String key) { + super("label=" + label + ",key=" + key + ",id=" + id); + } +} diff --git a/src/main/java/org/apache/tinkerpop/gremlin/tinkergraph/structure/SpecializedElementFactory.java b/src/main/java/org/apache/tinkerpop/gremlin/tinkergraph/structure/SpecializedElementFactory.java index f5855ed..eaf7996 100644 --- a/src/main/java/org/apache/tinkerpop/gremlin/tinkergraph/structure/SpecializedElementFactory.java +++ b/src/main/java/org/apache/tinkerpop/gremlin/tinkergraph/structure/SpecializedElementFactory.java @@ -18,7 +18,7 @@ */ package org.apache.tinkerpop.gremlin.tinkergraph.structure; -import org.apache.tinkerpop.gremlin.structure.Vertex; +import java.util.Map; /* To make use of specialized elements (for better memory/performance characteristics), you need to * create instances of these factories and register them with TinkerGraph. That way it will instantiate @@ -29,6 +29,12 @@ public interface ForVertex { V createVertex(Long id, TinkerGraph graph); VertexRef createVertexRef(V vertex); VertexRef createVertexRef(Long id, TinkerGraph graph); + + /** allows us to deserialize the correct property types without having to store that information in the binary */ + Map propertyTypeByIndex(); + + /** mapping from property index to property name */ + Map propertyNamesByIndex(); } public interface ForEdge { @@ -36,6 +42,12 @@ public interface ForEdge { E createEdge(Long id, TinkerGraph graph, VertexRef outVertex, VertexRef inVertex); EdgeRef createEdgeRef(E edge); EdgeRef createEdgeRef(Long id, TinkerGraph graph, VertexRef outVertex, VertexRef inVertex); + + /** allows us to deserialize the correct property types without having to store that information in the binary */ + Map propertyTypeByIndex(); + + /** mapping from property index to property name */ + Map propertyNamesByIndex(); } } diff --git a/src/main/java/org/apache/tinkerpop/gremlin/tinkergraph/structure/SpecializedTinkerEdge.java b/src/main/java/org/apache/tinkerpop/gremlin/tinkergraph/structure/SpecializedTinkerEdge.java index 347316e..d5020af 100644 --- a/src/main/java/org/apache/tinkerpop/gremlin/tinkergraph/structure/SpecializedTinkerEdge.java +++ b/src/main/java/org/apache/tinkerpop/gremlin/tinkergraph/structure/SpecializedTinkerEdge.java @@ -32,6 +32,9 @@ public abstract class SpecializedTinkerEdge extends TinkerEdge { private boolean modifiedSinceLastSerialization = true; private Semaphore modificationSemaphore = new Semaphore(1); + /** used for serializing this vertex - must be consistent between serializing and deserializing */ + public abstract SortedMap propertiesByStorageIdx(); + private final Set specificKeys; protected SpecializedTinkerEdge(TinkerGraph graph, Long id, Vertex outVertex, String label, Vertex inVertex, Set specificKeys) { @@ -124,4 +127,10 @@ public void releaseModificationLock() { modificationSemaphore.release(); } + protected void validateMandatoryProperty(String key, Object mandatoryValue) { + if (mandatoryValue == null) { + throw new MandatoryPropertyUndefinedException((long) id, label, key); + } + } + } diff --git a/src/main/java/org/apache/tinkerpop/gremlin/tinkergraph/structure/SpecializedTinkerVertex.java b/src/main/java/org/apache/tinkerpop/gremlin/tinkergraph/structure/SpecializedTinkerVertex.java index 4376e32..2620e90 100644 --- a/src/main/java/org/apache/tinkerpop/gremlin/tinkergraph/structure/SpecializedTinkerVertex.java +++ b/src/main/java/org/apache/tinkerpop/gremlin/tinkergraph/structure/SpecializedTinkerVertex.java @@ -77,8 +77,12 @@ protected VertexProperty specificProperty(String key) { /* implement in concrete specialised instance to avoid using generic HashMaps */ protected abstract Iterator> specificProperties(String key); + // TODO drop? public abstract Map valueMap(); + /** used for serializing this vertex - must be consistent between serializing and deserializing */ + public abstract SortedMap propertiesByStorageIdx(); + @Override public Iterator> properties(String... propertyKeys) { if (this.removed) return Collections.emptyIterator(); @@ -294,4 +298,10 @@ public void remove() { public void setModifiedSinceLastSerialization(boolean modifiedSinceLastSerialization) { this.modifiedSinceLastSerialization = modifiedSinceLastSerialization; } + + protected void validateMandatoryProperty(String key, Object mandatoryValue) { + if (mandatoryValue == null) { + throw new MandatoryPropertyUndefinedException((long) id, label, key); + } + } } diff --git a/src/main/java/org/apache/tinkerpop/gremlin/tinkergraph/structure/TinkerGraph.java b/src/main/java/org/apache/tinkerpop/gremlin/tinkergraph/structure/TinkerGraph.java index f3425a6..fc729f4 100644 --- a/src/main/java/org/apache/tinkerpop/gremlin/tinkergraph/structure/TinkerGraph.java +++ b/src/main/java/org/apache/tinkerpop/gremlin/tinkergraph/structure/TinkerGraph.java @@ -42,6 +42,7 @@ import org.apache.tinkerpop.gremlin.tinkergraph.process.computer.TinkerGraphComputerView; import org.apache.tinkerpop.gremlin.tinkergraph.process.traversal.strategy.optimization.TinkerGraphCountStrategy; import org.apache.tinkerpop.gremlin.tinkergraph.process.traversal.strategy.optimization.TinkerGraphStepStrategy; +import org.apache.tinkerpop.gremlin.tinkergraph.storage.Deserializer; import org.apache.tinkerpop.gremlin.tinkergraph.storage.EdgeDeserializer; import org.apache.tinkerpop.gremlin.tinkergraph.storage.EdgeSerializer; import org.apache.tinkerpop.gremlin.tinkergraph.storage.OndiskOverflow; @@ -50,6 +51,7 @@ import org.apache.tinkerpop.gremlin.tinkergraph.storage.VertexSerializer; import org.apache.tinkerpop.gremlin.util.iterator.IteratorUtils; import org.apache.tinkerpop.gremlin.util.iterator.MultiIterator; +import org.h2.mvstore.MVMap; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -541,6 +543,32 @@ public boolean isClosed() { return closed; } + /** reads specific property value from storage + * @param propertyType the base type - note that return value might be a `List`, which is why this method returns `Object`... :( */ + public Object readProperty(final Element element, final int propertyIdx, final Class propertyType) throws IOException { + final MVMap mvMap; + final Deserializer deserializer; + if (Vertex.class.isAssignableFrom(element.getClass())) { + mvMap = ondiskOverflow.getVertexMVMap(); + deserializer = ondiskOverflow.getVertexDeserializer().get(); + } + else if (Edge.class.isAssignableFrom(element.getClass())) { + mvMap = ondiskOverflow.getEdgeMVMap(); + deserializer = ondiskOverflow.getEdgeDeserializer().get(); + } + else throw new AssertionError("expected vertex or edge, but got " + element.getClass()); + + /** TODO: improve performance by reading only the relevant parts from disk + * easiest option is probably to store properties in separate MVMap + * positive side effect: we won't need to skip over the properties in `deserializer.unpackSpecificProperty` */ + final byte[] bytes = mvMap.get(element.id()); + return deserializer.unpackSpecificProperty(bytes, propertyIdx, propertyType); + } + + public OndiskOverflow getOndiskOverflow() { + return ondiskOverflow; + } + public class TinkerGraphFeatures implements Features { private final TinkerGraphGraphFeatures graphFeatures = new TinkerGraphGraphFeatures(); diff --git a/src/main/java/org/apache/tinkerpop/gremlin/tinkergraph/structure/VertexRef.java b/src/main/java/org/apache/tinkerpop/gremlin/tinkergraph/structure/VertexRef.java index 8fa04fa..373eae3 100644 --- a/src/main/java/org/apache/tinkerpop/gremlin/tinkergraph/structure/VertexRef.java +++ b/src/main/java/org/apache/tinkerpop/gremlin/tinkergraph/structure/VertexRef.java @@ -35,8 +35,8 @@ public VertexRef(final long vertexId, final String label, final TinkerGraph grap } @Override - protected V readFromDisk(final long vertexId) throws IOException { - return graph.ondiskOverflow.readVertex(vertexId); + protected V readFromDisk(final long vertexId, final boolean readProperties) throws IOException { + return graph.ondiskOverflow.readVertex(vertexId, readProperties); } @Override diff --git a/src/test/java/org/apache/tinkerpop/gremlin/tinkergraph/storage/SerializerTest.java b/src/test/java/org/apache/tinkerpop/gremlin/tinkergraph/storage/SerializerTest.java index 5031617..7616830 100644 --- a/src/test/java/org/apache/tinkerpop/gremlin/tinkergraph/storage/SerializerTest.java +++ b/src/test/java/org/apache/tinkerpop/gremlin/tinkergraph/storage/SerializerTest.java @@ -39,11 +39,12 @@ public void serializeVertex() throws IOException { SerializerTestVertex.STRING_PROPERTY, "StringValue", SerializerTestVertex.INT_PROPERTY, 42, SerializerTestVertex.STRING_LIST_PROPERTY, Arrays.asList("stringOne", "stringTwo"), - SerializerTestVertex.INT_LIST_PROPERTY, Arrays.asList(42, 43) + SerializerTestVertex.INT_LIST_PROPERTY, Arrays.asList(43, 44), + SerializerTestVertex.OPTIONAL_LONG_PROPERTY, 45l ); byte[] bytes = serializer.serialize(vertex); - Vertex deserialized = deserializer.deserialize(bytes); + Vertex deserialized = deserializer.deserialize(bytes, true); Vertex underlyingVertexDb = ((VertexRef) vertex).get(); assertEquals(underlyingVertexDb, deserialized); @@ -51,6 +52,25 @@ public void serializeVertex() throws IOException { final ElementRef deserializedRef = deserializer.deserializeRef(bytes); assertEquals(vertex.id(), deserializedRef.id); assertEquals(SerializerTestVertex.label, deserializedRef.label()); + + + // test reading of specific properties + SerializerTestVertex deserializedWithoutProps = (SerializerTestVertex) deserializer.deserialize(bytes, false); + assertNull("property should not have been initialized", deserializedWithoutProps.stringProperty); + assertNull("property should not have been initialized", deserializedWithoutProps.intProperty); + assertNull("property should not have been initialized", deserializedWithoutProps.stringListProperty); + assertNull("property should not have been initialized", deserializedWithoutProps.intListProperty); + assertNull("property should not have been initialized", deserializedWithoutProps.optionalLongProperty); + assertEquals("StringValue", deserializer.unpackSpecificProperty(bytes, SerializerTestVertex.STRING_PROPERTY_IDX, String.class)); + assertEquals(42, deserializer.unpackSpecificProperty(bytes, SerializerTestVertex.INT_PROPERTY_IDX, Integer.class)); + assertEquals(Arrays.asList("stringOne", "stringTwo"), deserializer.unpackSpecificProperty(bytes, SerializerTestVertex.STRING_LIST_PROPERTY_IDX, String.class)); + assertEquals(Arrays.asList(43, 44), deserializer.unpackSpecificProperty(bytes, SerializerTestVertex.INT_LIST_PROPERTY_IDX, Integer.class)); + assertEquals(45l, deserializer.unpackSpecificProperty(bytes, SerializerTestVertex.OPTIONAL_LONG_PROPERTY_IDX, Long.class)); + + // simulate normal case where bytes are lazily read from storage + graph.getOndiskOverflow().getVertexMVMap().put((Long) vertex.id(), bytes); + assertEquals("StringValue", deserializedWithoutProps.value(SerializerTestVertex.STRING_PROPERTY)); + assertEquals(Arrays.asList(43, 44), deserializedWithoutProps.value(SerializerTestVertex.INT_LIST_PROPERTY)); } } @@ -65,7 +85,7 @@ public void serializeEdge() throws IOException { Edge edge = v0.addEdge(SerializerTestEdge.label, v1, SerializerTestEdge.LONG_PROPERTY, Long.MAX_VALUE); byte[] bytes = serializer.serialize(edge); - Edge deserialized = deserializer.deserialize(bytes); + Edge deserialized = deserializer.deserialize(bytes, true); Edge underlyingEdgeDb = ((EdgeRef) edge).get(); assertEquals(underlyingEdgeDb, deserialized); @@ -73,6 +93,16 @@ public void serializeEdge() throws IOException { final ElementRef deserializedRef = deserializer.deserializeRef(bytes); assertEquals(edge.id(), deserializedRef.id); assertEquals(SerializerTestEdge.label, deserializedRef.label()); + + // test reading of specific properties + SerializerTestEdge deserializedWithoutProps = (SerializerTestEdge) deserializer.deserialize(bytes, false); + assertNull("property should not have been initialized", deserializedWithoutProps.longProperty); + assertEquals(Long.MAX_VALUE, deserializer.unpackSpecificProperty(bytes, SerializerTestEdge.LONG_PROPERTY_IDX, Long.class)); + + // simulate normal case where bytes are lazily read from storage + graph.getOndiskOverflow().getEdgeMVMap().put((Long) edge.id(), bytes); + Long value = deserializedWithoutProps.value(SerializerTestEdge.LONG_PROPERTY); // triggers reading from storage + assertEquals(Long.MAX_VALUE, value.longValue()); } } @@ -88,7 +118,7 @@ public void serializeVertexWithEdgeIds() throws IOException { Edge edge1 = vertex1.addEdge(SerializerTestEdge.label, vertex0); byte[] bytes = serializer.serialize(vertex0); - Vertex deserialized = deserializer.deserialize(bytes); + Vertex deserialized = deserializer.deserialize(bytes, true); Vertex underlyingVertexDb = ((VertexRef) vertex0).get(); assertEquals(underlyingVertexDb, deserialized); diff --git a/src/test/java/org/apache/tinkerpop/gremlin/tinkergraph/storage/SerializerTestEdge.java b/src/test/java/org/apache/tinkerpop/gremlin/tinkergraph/storage/SerializerTestEdge.java index 498427b..55de069 100644 --- a/src/test/java/org/apache/tinkerpop/gremlin/tinkergraph/storage/SerializerTestEdge.java +++ b/src/test/java/org/apache/tinkerpop/gremlin/tinkergraph/storage/SerializerTestEdge.java @@ -18,32 +18,63 @@ */ package org.apache.tinkerpop.gremlin.tinkergraph.storage; -import org.apache.tinkerpop.gremlin.tinkergraph.structure.*; - -import java.util.*; - +import gnu.trove.map.hash.THashMap; import org.apache.tinkerpop.gremlin.structure.Property; import org.apache.tinkerpop.gremlin.structure.Vertex; +import org.apache.tinkerpop.gremlin.tinkergraph.structure.EdgeRef; +import org.apache.tinkerpop.gremlin.tinkergraph.structure.SpecializedElementFactory; +import org.apache.tinkerpop.gremlin.tinkergraph.structure.SpecializedTinkerEdge; +import org.apache.tinkerpop.gremlin.tinkergraph.structure.TinkerGraph; +import org.apache.tinkerpop.gremlin.tinkergraph.structure.TinkerProperty; +import org.apache.tinkerpop.gremlin.tinkergraph.structure.VertexRef; + +import java.io.IOException; +import java.util.Arrays; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; +import java.util.SortedMap; +import java.util.TreeMap; public class SerializerTestEdge extends SpecializedTinkerEdge { public static final String label = "testEdge"; public static final String LONG_PROPERTY = "longProperty"; public static final Set SPECIFIC_KEYS = new HashSet<>(Arrays.asList(LONG_PROPERTY)); + public static final int LONG_PROPERTY_IDX = 0; - private Long longProperty; + Long longProperty; public SerializerTestEdge(TinkerGraph graph, long id, Vertex outVertex, Vertex inVertex) { super(graph, id, outVertex, label, inVertex, SPECIFIC_KEYS); } + @Override + public SortedMap propertiesByStorageIdx() { + SortedMap ret = new TreeMap<>(); + if (longProperty != null) ret.put(LONG_PROPERTY_IDX, longProperty); + return ret; + } + @Override protected Property specificProperty(String key) { - // note: use the statically defined strings to take advantage of `==` (pointer comparison) over `.equals` (String content comparison) for performance - if (LONG_PROPERTY.equals(key) && longProperty != null) { - return new TinkerProperty(this, key, longProperty); - } else { - return Property.empty(); + try { + final Object value; + final boolean mandatory; + switch (key) { + case LONG_PROPERTY: + if (longProperty == null) longProperty = (Long) graph.readProperty(this, LONG_PROPERTY_IDX, Long.class); + value = longProperty; + mandatory = true; + break; + default: + return Property.empty(); + } + + if (mandatory) validateMandatoryProperty(key, value); + return new TinkerProperty(this, key, value); + } catch (IOException e) { + throw new RuntimeException(e); } } @@ -86,5 +117,19 @@ public EdgeRef createEdgeRef(SerializerTestEdge edge) { public EdgeRef createEdgeRef(Long id, TinkerGraph graph, VertexRef outVertex, VertexRef inVertex) { return new EdgeRef<>(id, SerializerTestEdge.label, graph); } + + @Override + public Map propertyTypeByIndex() { + final Map ret = new THashMap<>(1); + ret.put(LONG_PROPERTY_IDX, Long.class); + return ret; + } + + @Override + public Map propertyNamesByIndex() { + final Map ret = new THashMap<>(1); + ret.put(LONG_PROPERTY_IDX, LONG_PROPERTY); + return ret; + } }; } diff --git a/src/test/java/org/apache/tinkerpop/gremlin/tinkergraph/storage/SerializerTestVertex.java b/src/test/java/org/apache/tinkerpop/gremlin/tinkergraph/storage/SerializerTestVertex.java index 0b63ac7..e173d2f 100644 --- a/src/test/java/org/apache/tinkerpop/gremlin/tinkergraph/storage/SerializerTestVertex.java +++ b/src/test/java/org/apache/tinkerpop/gremlin/tinkergraph/storage/SerializerTestVertex.java @@ -18,15 +18,29 @@ */ package org.apache.tinkerpop.gremlin.tinkergraph.storage; +import gnu.trove.map.hash.THashMap; import org.apache.tinkerpop.gremlin.structure.VertexProperty; -import org.apache.tinkerpop.gremlin.tinkergraph.structure.*; -import org.apache.tinkerpop.gremlin.tinkergraph.structure.specialized.gratefuldead.FollowedBy; -import org.apache.tinkerpop.gremlin.tinkergraph.structure.specialized.gratefuldead.SungBy; -import org.apache.tinkerpop.gremlin.tinkergraph.structure.specialized.gratefuldead.WrittenBy; +import org.apache.tinkerpop.gremlin.tinkergraph.structure.SpecializedElementFactory; +import org.apache.tinkerpop.gremlin.tinkergraph.structure.SpecializedTinkerVertex; +import org.apache.tinkerpop.gremlin.tinkergraph.structure.TinkerGraph; +import org.apache.tinkerpop.gremlin.tinkergraph.structure.TinkerVertexProperty; +import org.apache.tinkerpop.gremlin.tinkergraph.structure.VertexRef; import org.apache.tinkerpop.gremlin.util.iterator.IteratorUtils; +import java.io.IOException; import java.io.Serializable; -import java.util.*; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.Set; +import java.util.SortedMap; +import java.util.TreeMap; public class SerializerTestVertex extends SpecializedTinkerVertex implements Serializable { public static final String label = "testVertex"; @@ -35,15 +49,22 @@ public class SerializerTestVertex extends SpecializedTinkerVertex implements Ser public static final String INT_PROPERTY = "IntProperty"; public static final String STRING_LIST_PROPERTY = "StringListProperty"; public static final String INT_LIST_PROPERTY = "IntListProperty"; + public static final String OPTIONAL_LONG_PROPERTY = "OptLongProperty"; public static final Set SPECIFIC_KEYS = new HashSet<>(Arrays.asList(STRING_PROPERTY, INT_PROPERTY, STRING_LIST_PROPERTY, INT_LIST_PROPERTY)); public static final Set ALLOWED_IN_EDGE_LABELS = new HashSet<>(Arrays.asList(SerializerTestEdge.label)); public static final Set ALLOWED_OUT_EDGE_LABELS = new HashSet<>(Arrays.asList(SerializerTestEdge.label)); + public static final int STRING_PROPERTY_IDX = 0; + public static final int INT_PROPERTY_IDX = 1; + public static final int STRING_LIST_PROPERTY_IDX = 2; + public static final int INT_LIST_PROPERTY_IDX = 3; + public static final int OPTIONAL_LONG_PROPERTY_IDX = 4; // properties - private String stringProperty; - private Integer intProperty; - private List stringListProperty; - private List intListProperty; + String stringProperty; + Integer intProperty; + List stringListProperty; + List intListProperty; + Optional optionalLongProperty; public SerializerTestVertex(Long id, TinkerGraph graph) { super(id, SerializerTestVertex.label, graph); @@ -67,17 +88,46 @@ public Set allowedInEdgeLabels() { /* note: usage of `==` (pointer comparison) over `.equals` (String content comparison) is intentional for performance - use the statically defined strings */ @Override protected Iterator> specificProperties(String key) { - final VertexProperty ret; - if (STRING_PROPERTY.equals(key) && stringProperty != null) { - return IteratorUtils.of(new TinkerVertexProperty(this, key, stringProperty)); - } else if (key == STRING_LIST_PROPERTY && stringListProperty != null) { - return IteratorUtils.of(new TinkerVertexProperty(this, key, stringListProperty)); - } else if (key == INT_PROPERTY && intProperty != null) { - return IteratorUtils.of(new TinkerVertexProperty(this, key, intProperty)); - } else if (key == INT_LIST_PROPERTY && intListProperty != null) { - return IteratorUtils.of(new TinkerVertexProperty(this, key, intListProperty)); - } else { - return Collections.emptyIterator(); + try { + final Object value; + final boolean mandatory; + switch (key) { + case STRING_PROPERTY: + if (stringProperty == null) stringProperty = (String) graph.readProperty(this, STRING_PROPERTY_IDX, String.class); + value = stringProperty; + mandatory = true; + break; + case STRING_LIST_PROPERTY: + if (stringListProperty == null) stringListProperty = (List) graph.readProperty(this, STRING_LIST_PROPERTY_IDX, String.class); + value = stringListProperty; + mandatory = false; + break; + case INT_PROPERTY: + if (intProperty == null) intProperty = (Integer) graph.readProperty(this, INT_PROPERTY_IDX, Integer.class); + value = intProperty; + mandatory = true; + break; + case INT_LIST_PROPERTY: + if (intListProperty == null) intListProperty = (List) graph.readProperty(this, INT_LIST_PROPERTY_IDX, Integer.class); + value = intListProperty; + mandatory = false; + break; + case OPTIONAL_LONG_PROPERTY: + if (optionalLongProperty == null) optionalLongProperty = Optional.ofNullable((Long) graph.readProperty(this, OPTIONAL_LONG_PROPERTY_IDX, Long.class)); + if (!optionalLongProperty.isPresent()) { + return Collections.emptyIterator(); + } else { + value = optionalLongProperty.get(); + mandatory = false; + } + break; + default: + return Collections.emptyIterator(); + } + if (mandatory) validateMandatoryProperty(key, value); + return IteratorUtils.of(new TinkerVertexProperty(this, key, value)); + } catch (IOException e) { + throw new RuntimeException(e); } } @@ -88,9 +138,25 @@ public Map valueMap() { if (stringListProperty != null) properties.put(STRING_LIST_PROPERTY, stringListProperty); if (intProperty != null) properties.put(INT_PROPERTY, intProperty); if (intListProperty != null) properties.put(INT_LIST_PROPERTY, intListProperty); + if (optionalLongProperty != null) { + optionalLongProperty.ifPresent(value -> properties.put(OPTIONAL_LONG_PROPERTY, value)); + } return properties; } + @Override + public SortedMap propertiesByStorageIdx() { + SortedMap ret = new TreeMap<>(); + if (stringProperty != null) ret.put(STRING_PROPERTY_IDX, stringProperty); + if (intProperty != null) ret.put(INT_PROPERTY_IDX, intProperty); + if (stringListProperty != null) ret.put(STRING_LIST_PROPERTY_IDX, stringListProperty); + if (intListProperty != null) ret.put(INT_LIST_PROPERTY_IDX, intListProperty); + if (optionalLongProperty != null) { + optionalLongProperty.ifPresent(value -> ret.put(OPTIONAL_LONG_PROPERTY_IDX, value)); + } + return ret; + } + @Override protected VertexProperty updateSpecificProperty( VertexProperty.Cardinality cardinality, String key, V value) { @@ -112,6 +178,8 @@ protected VertexProperty updateSpecificProperty( if (this.intListProperty == null) this.intListProperty = new ArrayList<>(); this.intListProperty.add((Integer) value); } + } else if (OPTIONAL_LONG_PROPERTY.equals(key)) { + this.optionalLongProperty = (Optional) Optional.of(value); } else { throw new RuntimeException("property with key=" + key + " not (yet) supported by " + this.getClass().getName()); } @@ -128,6 +196,8 @@ protected void removeSpecificProperty(String key) { this.intProperty = null; } else if (INT_LIST_PROPERTY.equals(key)) { this.intListProperty = null; + } else if (OPTIONAL_LONG_PROPERTY.equals(key)) { + this.optionalLongProperty = null; } else { throw new RuntimeException("property with key=" + key + " not (yet) supported by " + this.getClass().getName()); } @@ -153,6 +223,28 @@ public VertexRef createVertexRef(SerializerTestVertex vert public VertexRef createVertexRef(Long id, TinkerGraph graph) { return new VertexRef<>(id, SerializerTestVertex.label, graph); } + + @Override + public Map propertyTypeByIndex() { + final Map ret = new THashMap<>(5); + ret.put(STRING_PROPERTY_IDX, String.class); + ret.put(INT_PROPERTY_IDX, Integer.class); + ret.put(STRING_LIST_PROPERTY_IDX, String.class); + ret.put(INT_LIST_PROPERTY_IDX, Integer.class); + ret.put(OPTIONAL_LONG_PROPERTY_IDX, Long.class); + return ret; + } + + @Override + public Map propertyNamesByIndex() { + final Map ret = new THashMap<>(5); + ret.put(STRING_PROPERTY_IDX, STRING_PROPERTY); + ret.put(INT_PROPERTY_IDX, INT_PROPERTY); + ret.put(STRING_LIST_PROPERTY_IDX, STRING_LIST_PROPERTY); + ret.put(INT_LIST_PROPERTY_IDX, INT_LIST_PROPERTY); + ret.put(OPTIONAL_LONG_PROPERTY_IDX, OPTIONAL_LONG_PROPERTY); + return ret; + } }; } diff --git a/src/test/java/org/apache/tinkerpop/gremlin/tinkergraph/structure/ReferenceManagerTest.java b/src/test/java/org/apache/tinkerpop/gremlin/tinkergraph/structure/ReferenceManagerTest.java deleted file mode 100644 index 0dce837..0000000 --- a/src/test/java/org/apache/tinkerpop/gremlin/tinkergraph/structure/ReferenceManagerTest.java +++ /dev/null @@ -1,67 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance - * with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ -package org.apache.tinkerpop.gremlin.tinkergraph.structure; - -import org.apache.commons.lang3.NotImplementedException; -import org.apache.tinkerpop.gremlin.structure.Property; -import org.junit.Test; - -import java.io.IOException; -import java.util.Iterator; - -import static org.junit.Assert.*; - -//TODO MP -public class ReferenceManagerTest { - - private int heapPercentageThreshold = 80; - - @Test - public void shouldReleaseConfiguredRefCount() { - int releaseCount = 2; -// ReferenceManager refMgr = new ReferenceManager(heapPercentageThreshold, releaseCount); -// refMgr.registerRef(new DummyElementRef()); -// refMgr.clearReferences(); - - - } - - - - private class DummyElementRef extends ElementRef { - public DummyElementRef(TinkerElement element) { - super(element); - } - - @Override - protected TinkerElement readFromDisk(long elementId) throws IOException { - throw new NotImplementedException(""); - } - - @Override - public Property property(String key, Object value) { - throw new NotImplementedException(""); - } - - @Override - public Iterator> properties(String... propertyKeys) { - throw new NotImplementedException(""); - } - } -} diff --git a/src/test/java/org/apache/tinkerpop/gremlin/tinkergraph/structure/SpecializedElementsWithOndiskTest.java b/src/test/java/org/apache/tinkerpop/gremlin/tinkergraph/structure/SpecializedElementsWithOndiskTest.java index f03ff58..2b50928 100644 --- a/src/test/java/org/apache/tinkerpop/gremlin/tinkergraph/structure/SpecializedElementsWithOndiskTest.java +++ b/src/test/java/org/apache/tinkerpop/gremlin/tinkergraph/structure/SpecializedElementsWithOndiskTest.java @@ -433,30 +433,30 @@ public void traversalPerformanceComparison() throws IOException { // with overflow that number should be tremendously larger, because only the reference wrappers are helt in memory // it'll be much slower due to the serialization to disk, but should not crash // important: use all the following vm opts: `-XX:+UseG1GC -Xms256m -Xmx256m -XX:+HeapDumpOnOutOfMemoryError` - public void shouldAllowGraphsLargerThanMemory() throws InterruptedException { - int vertexCount = 300000; -// TinkerGraph graph = newGratefulDeadGraphWithSpecializedElements(); - Configuration configuration = TinkerGraph.EMPTY_CONFIGURATION(); -// configuration.setProperty(TinkerGraph.GREMLIN_TINKERGRAPH_ONDISK_OVERFLOW_ENABLED, false); - configuration.setProperty(TinkerGraph.GREMLIN_TINKERGRAPH_ONDISK_OVERFLOW_ENABLED, true); - TinkerGraph graph = TinkerGraph.open( - configuration, - Arrays.asList(Song.factory, Artist.factory), - Arrays.asList(FollowedBy.factory, SungBy.factory, WrittenBy.factory) - ); - - for (long i = 0; i < vertexCount; i++) { - if (i % 50000 == 0) { - System.out.println(i + " vertices created"); - Thread.sleep(1000); // in lieu of other application usage - } - Vertex v = graph.addVertex(Song.label); -// v.property(Song.NAME, UUID.randomUUID().toString()); -// v.property(Song.SONG_TYPE, UUID.randomUUID().toString()); - v.property(Song.TEST_PROP, new int[200]); - } - graph.close(); - } +// public void shouldAllowGraphsLargerThanMemory() throws InterruptedException { +// int vertexCount = 300000; +//// TinkerGraph graph = newGratefulDeadGraphWithSpecializedElements(); +// Configuration configuration = TinkerGraph.EMPTY_CONFIGURATION(); +//// configuration.setProperty(TinkerGraph.GREMLIN_TINKERGRAPH_ONDISK_OVERFLOW_ENABLED, false); +// configuration.setProperty(TinkerGraph.GREMLIN_TINKERGRAPH_ONDISK_OVERFLOW_ENABLED, true); +// TinkerGraph graph = TinkerGraph.open( +// configuration, +// Arrays.asList(Song.factory, Artist.factory), +// Arrays.asList(FollowedBy.factory, SungBy.factory, WrittenBy.factory) +// ); +// +// for (long i = 0; i < vertexCount; i++) { +// if (i % 50000 == 0) { +// System.out.println(i + " vertices created"); +// Thread.sleep(1000); // in lieu of other application usage +// } +// Vertex v = graph.addVertex(Song.label); +//// v.property(Song.NAME, UUID.randomUUID().toString()); +//// v.property(Song.SONG_TYPE, UUID.randomUUID().toString()); +// v.property(Song.TEST_PROP, new int[200]); +// } +// graph.close(); +// } private TinkerGraph newGratefulDeadGraphWithSpecializedElements() { Configuration configuration = TinkerGraph.EMPTY_CONFIGURATION(); diff --git a/src/test/java/org/apache/tinkerpop/gremlin/tinkergraph/structure/specialized/gratefuldead/Artist.java b/src/test/java/org/apache/tinkerpop/gremlin/tinkergraph/structure/specialized/gratefuldead/Artist.java index 6a0bbe7..fb28ae6 100644 --- a/src/test/java/org/apache/tinkerpop/gremlin/tinkergraph/structure/specialized/gratefuldead/Artist.java +++ b/src/test/java/org/apache/tinkerpop/gremlin/tinkergraph/structure/specialized/gratefuldead/Artist.java @@ -18,6 +18,7 @@ */ package org.apache.tinkerpop.gremlin.tinkergraph.structure.specialized.gratefuldead; +import org.apache.commons.lang3.NotImplementedException; import org.apache.tinkerpop.gremlin.structure.Direction; import org.apache.tinkerpop.gremlin.structure.VertexProperty; import org.apache.tinkerpop.gremlin.tinkergraph.structure.*; @@ -84,6 +85,11 @@ public Map valueMap() { return properties; } + @Override + public SortedMap propertiesByStorageIdx() { + throw new NotImplementedException("TODO"); + } + @Override protected VertexProperty updateSpecificProperty( VertexProperty.Cardinality cardinality, String key, V value) { @@ -124,6 +130,16 @@ public VertexRef createVertexRef(Artist vertex) { public VertexRef createVertexRef(Long id, TinkerGraph graph) { return new VertexRef<>(id, Artist.label, graph); } + + @Override + public Map propertyTypeByIndex() { + throw new NotImplementedException("TODO"); + } + + @Override + public Map propertyNamesByIndex() { + throw new NotImplementedException("TODO"); + } }; } diff --git a/src/test/java/org/apache/tinkerpop/gremlin/tinkergraph/structure/specialized/gratefuldead/FollowedBy.java b/src/test/java/org/apache/tinkerpop/gremlin/tinkergraph/structure/specialized/gratefuldead/FollowedBy.java index 19e249a..14ba528 100644 --- a/src/test/java/org/apache/tinkerpop/gremlin/tinkergraph/structure/specialized/gratefuldead/FollowedBy.java +++ b/src/test/java/org/apache/tinkerpop/gremlin/tinkergraph/structure/specialized/gratefuldead/FollowedBy.java @@ -18,17 +18,19 @@ */ package org.apache.tinkerpop.gremlin.tinkergraph.structure.specialized.gratefuldead; +import gnu.trove.map.hash.THashMap; +import org.apache.commons.lang3.NotImplementedException; import org.apache.tinkerpop.gremlin.structure.Property; import org.apache.tinkerpop.gremlin.structure.Vertex; import org.apache.tinkerpop.gremlin.tinkergraph.structure.*; -import java.io.Serializable; import java.util.*; public class FollowedBy extends SpecializedTinkerEdge { public static final String label = "followedBy"; public static final String WEIGHT = "weight"; + public static final int WEIGHT_IDX = 0; public static final Set SPECIFIC_KEYS = new HashSet<>(Arrays.asList(WEIGHT)); private Integer weight; @@ -37,9 +39,18 @@ public FollowedBy(TinkerGraph graph, long id, Vertex outVertex, Vertex inVertex) super(graph, id, outVertex, label, inVertex, SPECIFIC_KEYS); } + @Override + public SortedMap propertiesByStorageIdx() { + SortedMap ret = new TreeMap<>(); + if (weight != null) { + ret.put(WEIGHT_IDX, weight); + } + return ret; + } + @Override protected Property specificProperty(String key) { - // note: use the statically defined strings to take advantage of `==` (pointer comparison) over `.equals` (String content comparison) for performance + // note: use the statically defined strings to take advantage of `==` (pointer comparison) over `.equals` (String content comparison) for performance if (WEIGHT.equals(key) && weight != null) { return new TinkerProperty(this, key, weight); } else { @@ -86,5 +97,17 @@ public EdgeRef createEdgeRef(FollowedBy edge) { public EdgeRef createEdgeRef(Long id, TinkerGraph graph, VertexRef outVertex, VertexRef inVertex) { return new EdgeRef<>(id, FollowedBy.label, graph); } + + @Override + public Map propertyTypeByIndex() { + final Map ret = new THashMap<>(1); + ret.put(WEIGHT_IDX, Integer.class); + return ret; + } + + @Override + public Map propertyNamesByIndex() { + throw new NotImplementedException("TODO"); + } }; } diff --git a/src/test/java/org/apache/tinkerpop/gremlin/tinkergraph/structure/specialized/gratefuldead/Song.java b/src/test/java/org/apache/tinkerpop/gremlin/tinkergraph/structure/specialized/gratefuldead/Song.java index 464d77d..2d682e1 100644 --- a/src/test/java/org/apache/tinkerpop/gremlin/tinkergraph/structure/specialized/gratefuldead/Song.java +++ b/src/test/java/org/apache/tinkerpop/gremlin/tinkergraph/structure/specialized/gratefuldead/Song.java @@ -18,6 +18,7 @@ */ package org.apache.tinkerpop.gremlin.tinkergraph.structure.specialized.gratefuldead; +import org.apache.commons.lang3.NotImplementedException; import org.apache.tinkerpop.gremlin.structure.Direction; import org.apache.tinkerpop.gremlin.structure.VertexProperty; import org.apache.tinkerpop.gremlin.tinkergraph.structure.*; @@ -32,16 +33,17 @@ public class Song extends SpecializedTinkerVertex implements Serializable { public static final String NAME = "name"; public static final String SONG_TYPE = "songType"; public static final String PERFORMANCES = "performances"; - public static final String TEST_PROP = "testProperty"; - public static final Set SPECIFIC_KEYS = new HashSet<>(Arrays.asList(NAME, SONG_TYPE, PERFORMANCES, TEST_PROP)); + public static final Set SPECIFIC_KEYS = new HashSet<>(Arrays.asList(NAME, SONG_TYPE, PERFORMANCES)); public static final Set ALLOWED_IN_EDGE_LABELS = new HashSet<>(Arrays.asList(FollowedBy.label)); public static final Set ALLOWED_OUT_EDGE_LABELS = new HashSet<>(Arrays.asList(FollowedBy.label, SungBy.label, WrittenBy.label)); + public static final int NAME_IDX = 0; + public static final int SONG_TYPE_IDX = 1; + public static final int PERFORMANCES_IDX = 2; // properties private String name; private String songType; private Integer performances; - private int[] testProp; public Song(Long id, TinkerGraph graph) { super(id, Song.label, graph); @@ -75,8 +77,6 @@ public Integer getPerformances() { return performances; } - public int[] getTestProp() { return testProp; } - @Override protected Set specificKeys() { return SPECIFIC_KEYS; @@ -102,8 +102,6 @@ protected Iterator> specificProperties(String key) { return IteratorUtils.of(new TinkerVertexProperty(this, key, songType)); } else if (key == PERFORMANCES && performances != null) { return IteratorUtils.of(new TinkerVertexProperty(this, key, performances)); - } else if (key == TEST_PROP && testProp != null) { - return IteratorUtils.of(new TinkerVertexProperty(this, key, testProp)); } else { return Collections.emptyIterator(); } @@ -115,10 +113,18 @@ public Map valueMap() { if (name != null) properties.put(NAME, name); if (songType != null) properties.put(SONG_TYPE, songType); if (performances != null) properties.put(PERFORMANCES, performances); - if (testProp != null) properties.put(TEST_PROP, testProp); return properties; } + @Override + public SortedMap propertiesByStorageIdx() { + SortedMap ret = new TreeMap<>(); + if (name != null) ret.put(NAME_IDX, name); + if (songType != null) ret.put(SONG_TYPE_IDX, songType); + if (performances != null) ret.put(PERFORMANCES_IDX, performances); + return ret; + } + @Override protected VertexProperty updateSpecificProperty( VertexProperty.Cardinality cardinality, String key, V value) { @@ -128,8 +134,6 @@ protected VertexProperty updateSpecificProperty( this.songType = (String) value; } else if (PERFORMANCES.equals(key)) { this.performances = ((Integer) value); - } else if (TEST_PROP.equals(key)) { - this.testProp = (int[]) value; } else { throw new RuntimeException("property with key=" + key + " not (yet) supported by " + this.getClass().getName()); } @@ -145,8 +149,6 @@ protected void removeSpecificProperty(String key) { this.songType = null; } else if (PERFORMANCES.equals(key)) { this.performances = null; - } else if (TEST_PROP.equals(key)) { - this.testProp = null; } else { throw new RuntimeException("property with key=" + key + " not (yet) supported by " + this.getClass().getName()); } @@ -172,6 +174,16 @@ public VertexRef createVertexRef(Song vertex) { public VertexRef createVertexRef(Long id, TinkerGraph graph) { return new VertexRef<>(id, Song.label, graph); } + + @Override + public Map propertyTypeByIndex() { + throw new NotImplementedException("TODO"); + } + + @Override + public Map propertyNamesByIndex() { + throw new NotImplementedException("TODO"); + } }; } diff --git a/src/test/java/org/apache/tinkerpop/gremlin/tinkergraph/structure/specialized/gratefuldead/SungBy.java b/src/test/java/org/apache/tinkerpop/gremlin/tinkergraph/structure/specialized/gratefuldead/SungBy.java index 27badfd..36a135b 100644 --- a/src/test/java/org/apache/tinkerpop/gremlin/tinkergraph/structure/specialized/gratefuldead/SungBy.java +++ b/src/test/java/org/apache/tinkerpop/gremlin/tinkergraph/structure/specialized/gratefuldead/SungBy.java @@ -18,6 +18,7 @@ */ package org.apache.tinkerpop.gremlin.tinkergraph.structure.specialized.gratefuldead; +import org.apache.commons.lang3.NotImplementedException; import org.apache.tinkerpop.gremlin.structure.Property; import org.apache.tinkerpop.gremlin.structure.Vertex; import org.apache.tinkerpop.gremlin.tinkergraph.structure.*; @@ -34,6 +35,11 @@ public SungBy(TinkerGraph graph, long id, Vertex outVertex, Vertex inVertex) { super(graph, id, outVertex, label, inVertex, SPECIFIC_KEYS); } + @Override + public SortedMap propertiesByStorageIdx() { + throw new NotImplementedException("TODO"); + } + @Override protected Property specificProperty(String key) { return Property.empty(); @@ -69,6 +75,16 @@ public EdgeRef createEdgeRef(SungBy edge) { public EdgeRef createEdgeRef(Long id, TinkerGraph graph, VertexRef outVertex, VertexRef inVertex) { return new EdgeRef<>(id, SungBy.label, graph); } + + @Override + public Map propertyTypeByIndex() { + throw new NotImplementedException("TODO"); + } + + @Override + public Map propertyNamesByIndex() { + throw new NotImplementedException("TODO"); + } }; diff --git a/src/test/java/org/apache/tinkerpop/gremlin/tinkergraph/structure/specialized/gratefuldead/WrittenBy.java b/src/test/java/org/apache/tinkerpop/gremlin/tinkergraph/structure/specialized/gratefuldead/WrittenBy.java index b83726a..5600711 100644 --- a/src/test/java/org/apache/tinkerpop/gremlin/tinkergraph/structure/specialized/gratefuldead/WrittenBy.java +++ b/src/test/java/org/apache/tinkerpop/gremlin/tinkergraph/structure/specialized/gratefuldead/WrittenBy.java @@ -18,6 +18,7 @@ */ package org.apache.tinkerpop.gremlin.tinkergraph.structure.specialized.gratefuldead; +import org.apache.commons.lang3.NotImplementedException; import org.apache.tinkerpop.gremlin.structure.Property; import org.apache.tinkerpop.gremlin.structure.Vertex; import org.apache.tinkerpop.gremlin.tinkergraph.structure.*; @@ -34,6 +35,11 @@ public WrittenBy(TinkerGraph graph, long id, Vertex outVertex, Vertex inVertex) super(graph, id, outVertex, label, inVertex, SPECIFIC_KEYS); } + @Override + public SortedMap propertiesByStorageIdx() { + throw new NotImplementedException("TODO"); + } + @Override protected Property specificProperty(String key) { return Property.empty(); @@ -69,5 +75,15 @@ public EdgeRef createEdgeRef(WrittenBy edge) { public EdgeRef createEdgeRef(Long id, TinkerGraph graph, VertexRef outVertex, VertexRef inVertex) { return new EdgeRef<>(id, WrittenBy.label, graph); } + + @Override + public Map propertyTypeByIndex() { + throw new NotImplementedException("TODO"); + } + + @Override + public Map propertyNamesByIndex() { + throw new NotImplementedException("TODO"); + } }; }