blob: c270312b09314bdf4f6cc0ac1852894149191692 [file] [log] [blame]
/*
* 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.hugegraph.structure;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Set;
import org.apache.commons.collections.CollectionUtils;
import org.apache.hugegraph.HugeException;
import org.apache.hugegraph.HugeGraph;
import org.apache.hugegraph.backend.id.Id;
import org.apache.hugegraph.backend.id.IdGenerator;
import org.apache.hugegraph.backend.id.SnowflakeIdGenerator;
import org.apache.hugegraph.backend.id.SplicingIdGenerator;
import org.apache.hugegraph.backend.query.ConditionQuery;
import org.apache.hugegraph.backend.query.Query;
import org.apache.hugegraph.backend.query.QueryResults;
import org.apache.hugegraph.backend.serializer.BytesBuffer;
import org.apache.hugegraph.backend.tx.GraphTransaction;
import org.apache.hugegraph.config.CoreOptions;
import org.apache.hugegraph.perf.PerfUtil.Watched;
import org.apache.hugegraph.schema.EdgeLabel;
import org.apache.hugegraph.schema.PropertyKey;
import org.apache.hugegraph.schema.VertexLabel;
import org.apache.hugegraph.type.HugeType;
import org.apache.hugegraph.type.define.Cardinality;
import org.apache.hugegraph.type.define.CollectionType;
import org.apache.hugegraph.type.define.Directions;
import org.apache.hugegraph.type.define.HugeKeys;
import org.apache.hugegraph.type.define.IdStrategy;
import org.apache.hugegraph.util.E;
import org.apache.hugegraph.util.collection.CollectionFactory;
import org.apache.logging.log4j.util.Strings;
import org.apache.tinkerpop.gremlin.structure.Direction;
import org.apache.tinkerpop.gremlin.structure.Edge;
import org.apache.tinkerpop.gremlin.structure.T;
import org.apache.tinkerpop.gremlin.structure.Vertex;
import org.apache.tinkerpop.gremlin.structure.VertexProperty;
import org.apache.tinkerpop.gremlin.structure.util.ElementHelper;
import org.apache.tinkerpop.gremlin.structure.util.StringFactory;
import org.apache.tinkerpop.gremlin.structure.util.empty.EmptyVertexProperty;
import com.google.common.collect.ImmutableList;
public class HugeVertex extends HugeElement implements Vertex, Cloneable {
private static final List<HugeEdge> EMPTY_LIST = ImmutableList.of();
private Id id;
private VertexLabel label;
protected Collection<HugeEdge> edges;
public HugeVertex(final HugeGraph graph, Id id, VertexLabel label) {
super(graph);
E.checkArgumentNotNull(label, "Vertex label can't be null");
this.label = label;
this.id = id;
this.edges = EMPTY_LIST;
if (this.id != null) {
if (label.idStrategy() == IdStrategy.CUSTOMIZE_UUID) {
this.assignId(id);
} else {
this.checkIdLength();
}
}
}
@Override
public HugeType type() {
return HugeType.VERTEX;
}
@Override
public Id id() {
return this.id;
}
@Override
public VertexLabel schemaLabel() {
assert VertexLabel.OLAP_VL.equals(this.label) ||
this.graph().sameAs(this.label.graph());
return this.label;
}
@Override
public String name() {
E.checkState(this.label.idStrategy() == IdStrategy.PRIMARY_KEY,
"Only primary key vertex has name, " +
"but got '%s' with id strategy '%s'",
this, this.label.idStrategy());
String name;
if (this.id != null) {
String[] parts = SplicingIdGenerator.parse(this.id);
E.checkState(parts.length == 2,
"Invalid primary key vertex id '%s'", this.id);
name = parts[1];
} else {
assert this.id == null;
List<Object> propValues = this.primaryValues();
E.checkState(!propValues.isEmpty(),
"Primary values must not be empty " +
"(has properties %s)", hasProperties());
name = ConditionQuery.concatValues(propValues);
E.checkArgument(!name.isEmpty(),
"The value of primary key can't be empty");
}
return name;
}
public void assignId(Id id) {
this.assignId(id, false);
}
@Watched(prefix = "vertex")
public void assignId(Id id, boolean force) {
IdStrategy strategy = this.label.idStrategy();
// Generate an id and assign
switch (strategy) {
case CUSTOMIZE_STRING:
assert !id.number();
this.id = id;
break;
case CUSTOMIZE_NUMBER:
assert id.number();
this.id = id;
break;
case CUSTOMIZE_UUID:
this.id = id.uuid() ? id : IdGenerator.of(id.asString(), true);
break;
case PRIMARY_KEY:
this.id = SplicingIdGenerator.instance().generate(this);
break;
case AUTOMATIC:
if (force) {
// Resume id for AUTOMATIC id strategy in restoring mode
assert id.number();
this.id = id;
} else {
this.id = SnowflakeIdGenerator.instance(this.graph())
.generate(this);
}
break;
default:
throw new AssertionError(String.format("Unknown id strategy '%s'", strategy));
}
this.checkIdLength();
}
protected void checkIdLength() {
assert this.id != null;
int len = this.id.asBytes().length;
if (len <= BytesBuffer.ID_LEN_MAX) {
return;
}
E.checkArgument(false,
"The max length of vertex id is %s, but got %s {%s}",
BytesBuffer.ID_LEN_MAX, len, this.id);
}
@Override
public String label() {
return this.schemaLabel().name();
}
public void correctVertexLabel(VertexLabel correctLabel) {
E.checkArgumentNotNull(correctLabel, "Vertex label can't be null");
if (this.label != null && !this.label.undefined() &&
!correctLabel.undefined()) {
E.checkArgument(this.label.equals(correctLabel),
"Vertex label can't be changed from '%s' to '%s'",
this.label, correctLabel);
}
this.label = correctLabel;
}
@Watched(prefix = "vertex")
protected List<Object> primaryValues() {
E.checkArgument(this.label.idStrategy() == IdStrategy.PRIMARY_KEY,
"The id strategy '%s' don't have primary keys",
this.label.idStrategy());
List<Id> primaryKeys = this.label.primaryKeys();
E.checkArgument(!primaryKeys.isEmpty(),
"Primary key can't be empty for id strategy '%s'",
IdStrategy.PRIMARY_KEY);
boolean encodeNumber = this.graph()
.option(CoreOptions.VERTEX_ENCODE_PK_NUMBER);
List<Object> propValues = new ArrayList<>(primaryKeys.size());
for (Id pk : primaryKeys) {
HugeProperty<?> property = this.getProperty(pk);
E.checkState(property != null,
"The value of primary key '%s' can't be null",
this.graph().propertyKey(pk).name());
Object propValue = property.serialValue(encodeNumber);
if (Strings.EMPTY.equals(propValue)) {
propValue = ConditionQuery.INDEX_VALUE_EMPTY;
}
propValues.add(propValue);
}
return propValues;
}
public boolean existsEdges() {
return this.edges.size() > 0;
}
public Collection<HugeEdge> getEdges() {
return Collections.unmodifiableCollection(this.edges);
}
public void resetEdges() {
/*
* Use List to hold edges to reduce memory usage and operation time.
*/
this.edges = newList();
}
public void removeEdge(HugeEdge edge) {
this.edges.remove(edge);
}
public void addEdge(HugeEdge edge) {
if (this.edges == EMPTY_LIST) {
this.edges = newList();
}
this.edges.add(edge);
}
/**
* Add one edge between this vertex and other vertex
* <p>
* *** this method is not thread safe, must clone this vertex first before
* multi thread access e.g. `vertex.copy().resetTx();` ***
*/
@Watched(prefix = "vertex")
@Override
public HugeEdge addEdge(String label, Vertex vertex, Object... keyValues) {
HugeVertex targetVertex = (HugeVertex) vertex;
HugeEdge edge = this.constructEdge(label, targetVertex, keyValues);
// Attach edge to vertex
this.addOutEdge(edge);
targetVertex.addInEdge(edge.switchOwner());
if (this.fresh()) {
this.graph().canAddEdge(edge);
return this.tx().addEdge(edge);
} else {
return (HugeEdge) this.graph().addEdge(edge);
}
}
public HugeEdge constructEdge(String label, HugeVertex vertex,
Object... keyValues) {
ElementKeys elemKeys = classifyKeys(keyValues);
// Check id (must be null)
if (elemKeys.id() != null) {
throw Edge.Exceptions.userSuppliedIdsNotSupported();
}
Id id = null;
// Check target vertex
E.checkArgumentNotNull(vertex, "Target vertex can't be null");
// Check label
E.checkArgument(label != null && !label.isEmpty(),
"Edge label can't be null or empty");
EdgeLabel edgeLabel = this.graph().edgeLabel(label);
// Check link
E.checkArgument(edgeLabel.checkLinkEqual(this.schemaLabel().id(),
vertex.schemaLabel().id()),
"Undefined link of edge label '%s': '%s' -> '%s'",
label, this.label(), vertex.label());
// Check sortKeys
List<Id> keys = this.graph().mapPkName2Id(elemKeys.keys());
E.checkArgument(new HashSet<>(keys).containsAll(edgeLabel.sortKeys()),
"The sort key(s) must be set for the edge " +
"with label: '%s'", edgeLabel.name());
// Check whether passed all non-null props
@SuppressWarnings("unchecked")
Collection<Id> nonNullKeys = CollectionUtils.subtract(
edgeLabel.properties(),
edgeLabel.nullableKeys());
if (!new HashSet<>(keys).containsAll(nonNullKeys)) {
@SuppressWarnings("unchecked")
Collection<Id> missed = CollectionUtils.subtract(nonNullKeys, keys);
E.checkArgument(false, "All non-null property keys: %s " +
"of edge label '%s' must be set, " +
"but missed keys: %s",
this.graph().mapPkId2Name(nonNullKeys),
edgeLabel.name(),
this.graph().mapPkId2Name(missed));
}
HugeEdge edge = new HugeEdge(this, id, edgeLabel, vertex);
// Set properties
ElementHelper.attachProperties(edge, keyValues);
edge.assignId();
return edge;
}
/**
* Add edge with direction OUT
* @param edge the out edge
*/
@Watched
public void addOutEdge(HugeEdge edge) {
if (edge.ownerVertex() == null) {
edge.sourceVertex(this);
}
E.checkState(edge.isDirection(Directions.OUT),
"The owner vertex('%s') of OUT edge '%s' should be '%s'",
edge.ownerVertex().id(), edge, this.id());
this.addEdge(edge);
}
/**
* Add edge with direction IN
* @param edge the in edge
*/
@Watched
public void addInEdge(HugeEdge edge) {
if (edge.ownerVertex() == null) {
edge.targetVertex(this);
}
E.checkState(edge.isDirection(Directions.IN),
"The owner vertex('%s') of IN edge '%s' should be '%s'",
edge.ownerVertex().id(), edge, this.id());
this.addEdge(edge);
}
public Iterator<Edge> getEdges(Directions direction, String... edgeLabels) {
List<Edge> list = new LinkedList<>();
for (HugeEdge edge : this.edges) {
if (edge.matchDirection(direction) &&
edge.belongToLabels(edgeLabels)) {
list.add(edge);
}
}
return list.iterator();
}
public Iterator<Vertex> getVertices(Directions direction,
String... edgeLabels) {
List<Vertex> list = new LinkedList<>();
Iterator<Edge> edges = this.getEdges(direction, edgeLabels);
while (edges.hasNext()) {
HugeEdge edge = (HugeEdge) edges.next();
list.add(edge.otherVertex(this));
}
return list.iterator();
}
@Watched(prefix = "vertex")
@Override
public Iterator<Edge> edges(Direction tinkerpopDir, String... edgeLabels) {
Directions direction = Directions.convert(tinkerpopDir);
Id[] edgeLabelIds = this.graph().mapElName2Id(edgeLabels);
Query query = GraphTransaction.constructEdgesQuery(this.id(), direction,
edgeLabelIds);
return this.graph().edges(query);
}
@Watched(prefix = "vertex")
@Override
public Iterator<Vertex> vertices(Direction direction,
String... edgeLabels) {
Iterator<Edge> edges = this.edges(direction, edgeLabels);
return this.graph().adjacentVertices(edges);
}
@Watched(prefix = "vertex")
@Override
public void remove() {
this.removed(true);
/*
* Call by tx or by graph to remove vertex,
* call by tx if the vertex is new because the context is dependent
*/
GraphTransaction tx = this.tx();
if (tx != null) {
assert this.fresh();
tx.removeVertex(this);
} else {
assert !this.fresh();
this.graph().removeVertex(this);
}
}
@Watched(prefix = "vertex")
@Override
public <V> VertexProperty<V> property(VertexProperty.Cardinality cardinality,
String key, V value, Object... objects) {
if (objects.length != 0 && objects[0].equals(T.id)) {
throw VertexProperty.Exceptions.userSuppliedIdsNotSupported();
}
// TODO: extra props: objects
if (objects.length != 0) {
throw VertexProperty.Exceptions.metaPropertiesNotSupported();
}
PropertyKey propertyKey = this.graph().propertyKey(key);
/*
* g.AddV("xxx").property("key1", val1).property("key2", val2)
* g.AddV("xxx").property(single, "key1", val1)
* .property(list, "key2", val2)
*
* The cardinality single may be user supplied single, it may also be
* that user doesn't supply cardinality, when it is latter situation,
* we shouldn't check it. Because of this reason, we are forced to
* give up the check of user supplied cardinality single.
* The cardinality not single must be user supplied, so should check it
*/
if (cardinality != VertexProperty.Cardinality.single) {
E.checkArgument(propertyKey.cardinality() ==
Cardinality.convert(cardinality),
"Invalid cardinality '%s' for property key '%s', " +
"expect '%s'", cardinality, key,
propertyKey.cardinality().string());
}
// Check key in vertex label
E.checkArgument(VertexLabel.OLAP_VL.equals(this.label) ||
this.label.properties().contains(propertyKey.id()),
"Invalid property '%s' for vertex label '%s'",
key, this.label);
// Primary-Keys can only be set once
if (this.schemaLabel().primaryKeys().contains(propertyKey.id())) {
E.checkArgument(!this.hasProperty(propertyKey.id()),
"Can't update primary key: '%s'", key);
}
if (value == null) {
this.removeProperty(propertyKey.id());
return EmptyVertexProperty.instance();
}
@SuppressWarnings("unchecked")
VertexProperty<V> prop = (VertexProperty<V>) this.addProperty(propertyKey,
value, !this.fresh());
return prop;
}
@Override
protected GraphTransaction tx() {
return null;
}
@Watched(prefix = "vertex")
@Override
protected <V> HugeVertexProperty<V> newProperty(PropertyKey pkey, V val) {
return new HugeVertexProperty<>(this, pkey, val);
}
@Watched(prefix = "vertex")
@Override
protected <V> void onUpdateProperty(Cardinality cardinality,
HugeProperty<V> prop) {
if (prop != null) {
assert prop instanceof HugeVertexProperty;
/*
* Call tx or graph to update property,
* call by tx if the vertex is new because the context is dependent
* (should update cache even if it's new)
*/
GraphTransaction tx = this.tx();
if (tx != null) {
assert this.fresh();
tx.addVertexProperty((HugeVertexProperty<V>) prop);
} else {
assert !this.fresh();
this.graph().addVertexProperty((HugeVertexProperty<V>) prop);
}
}
}
@Watched(prefix = "vertex")
@Override
protected boolean ensureFilledProperties(boolean throwIfNotExist) {
if (this.isPropLoaded()) {
this.updateToDefaultValueIfNone();
return true;
}
// Skip query if there is no any property key in schema
if (this.schemaLabel().properties().isEmpty()) {
this.propLoaded();
return true;
}
// NOTE: only adjacent vertex will reach here
Iterator<Vertex> vertices = this.graph().adjacentVertex(this.id());
HugeVertex vertex = (HugeVertex) QueryResults.one(vertices);
if (vertex == null && !throwIfNotExist) {
return false;
}
E.checkState(vertex != null, "Vertex '%s' does not exist", this.id);
if (vertex.schemaLabel().undefined() ||
!vertex.schemaLabel().equals(this.schemaLabel())) {
// Update vertex label of dangling edge to undefined
this.correctVertexLabel(VertexLabel.undefined(this.graph()));
vertex.resetProperties();
}
this.copyProperties(vertex);
this.updateToDefaultValueIfNone();
return true;
}
@Watched(prefix = "vertex")
@SuppressWarnings("unchecked") // (VertexProperty<V>) prop
@Override
public <V> Iterator<VertexProperty<V>> properties(String... keys) {
// TODO: Compatible with TinkerPop properties() (HugeGraph-742)
this.ensureFilledProperties(true);
// Capacity should be about the following size
int propsCapacity = keys.length == 0 ?
this.sizeOfProperties() :
keys.length;
List<VertexProperty<V>> props = new ArrayList<>(propsCapacity);
if (keys.length == 0) {
for (HugeProperty<?> prop : this.getProperties()) {
assert prop instanceof VertexProperty;
props.add((VertexProperty<V>) prop);
}
} else {
for (String key : keys) {
Id pkeyId;
try {
pkeyId = this.graph().propertyKey(key).id();
} catch (IllegalArgumentException ignored) {
continue;
}
HugeProperty<?> prop = this.getProperty(pkeyId);
if (prop == null) {
// Not found
continue;
}
assert prop instanceof VertexProperty;
props.add((VertexProperty<V>) prop);
}
}
return props.iterator();
}
@Override
public Object sysprop(HugeKeys key) {
switch (key) {
case ID:
return this.id();
case LABEL:
return this.schemaLabel().id();
case PRIMARY_VALUES:
return this.name();
case PROPERTIES:
return this.getPropertiesMap();
default:
E.checkArgument(false,
"Invalid system property '%s' of Vertex", key);
return null;
}
}
public boolean valid() {
try {
return this.ensureFilledProperties(false);
} catch (Throwable e) {
// Generally the program can't get here
return false;
}
}
/**
* Clear edges/properties of the cloned vertex, and set `removed` true
* @return a new vertex
*/
public HugeVertex prepareRemoved() {
// NOTE: clear edges/properties of the cloned vertex and return
HugeVertex vertex = this.clone();
// Remove self
vertex.removed(true);
vertex.resetEdges();
vertex.resetProperties();
return vertex;
}
public boolean olap() {
return this.label.olap();
}
@Override
public HugeVertex copy() {
HugeVertex vertex = this.clone();
vertex.copyProperties(this);
return vertex;
}
@Override
protected HugeVertex clone() {
try {
return (HugeVertex) super.clone();
} catch (CloneNotSupportedException e) {
throw new HugeException("Failed to clone HugeVertex", e);
}
}
@Override
public String toString() {
return StringFactory.vertexString(this);
}
public static final Id getIdValue(Object idValue) {
return HugeElement.getIdValue(idValue);
}
public static HugeVertex undefined(HugeGraph graph, Id id) {
VertexLabel label = VertexLabel.undefined(graph);
return new HugeVertex(graph, id, label);
}
public static HugeVertex create(final GraphTransaction tx,
Id id, VertexLabel label) {
return new HugeVertex4Insert(tx, id, label);
}
private static <V> Set<V> newSet() {
return CollectionFactory.newSet(CollectionType.EC);
}
private static <V> List<V> newList() {
return CollectionFactory.newList(CollectionType.EC);
}
private static final class HugeVertex4Insert extends HugeVertex {
private GraphTransaction tx;
public HugeVertex4Insert(final GraphTransaction tx,
Id id, VertexLabel label) {
super(tx.graph(), id, label);
/*
* Use Set to hold edges inserted into vertex
* to avoid duplicated edges
*/
this.edges = newSet();
this.tx = tx;
this.fresh(true);
}
@Override
public void resetEdges() {
this.edges = newSet();
}
@Override
public void addEdge(HugeEdge edge) {
if (this.edges == EMPTY_LIST) {
this.edges = newSet();
}
this.edges.add(edge);
}
@Override
public void committed() {
super.committed();
this.tx = null;
}
@Override
protected GraphTransaction tx() {
if (this.fresh()) {
E.checkNotNull(this.tx, "tx");
return this.tx;
}
return null;
}
}
}