All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.arcadedb.graph.GraphEngine Maven / Gradle / Ivy

There is a newer version: 24.11.2
Show newest version
/*
 * Copyright © 2021-present Arcade Data Ltd ([email protected])
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 * SPDX-FileCopyrightText: 2021-present Arcade Data Ltd ([email protected])
 * SPDX-License-Identifier: Apache-2.0
 */
package com.arcadedb.graph;

import com.arcadedb.database.Database;
import com.arcadedb.database.DatabaseInternal;
import com.arcadedb.database.Identifiable;
import com.arcadedb.database.MutableDocument;
import com.arcadedb.database.RID;
import com.arcadedb.engine.Bucket;
import com.arcadedb.exception.RecordNotFoundException;
import com.arcadedb.exception.SchemaException;
import com.arcadedb.log.LogManager;
import com.arcadedb.schema.DocumentType;
import com.arcadedb.schema.EdgeType;
import com.arcadedb.schema.VertexType;
import com.arcadedb.utility.MultiIterator;
import com.arcadedb.utility.Pair;

import java.util.*;
import java.util.concurrent.atomic.*;
import java.util.logging.*;

/**
 * Central class to work with graphs. This is not intended to be used by the end user, but rather from Vertex and Edge classes.
 *
 * @author Luca Garulli ([email protected])
 */
public class GraphEngine {
  public static final String           OUT_EDGES_SUFFIX = "_out_edges";
  public static final String           IN_EDGES_SUFFIX  = "_in_edges";
  private final       DatabaseInternal database;

  public GraphEngine(final DatabaseInternal database) {
    this.database = database;
  }

  public static class CreateEdgeOperation {
    final String       edgeTypeName;
    final Identifiable destinationVertex;
    final Object[]     edgeProperties;

    public CreateEdgeOperation(final String edgeTypeName, final Identifiable destinationVertex, final Object[] edgeProperties) {
      this.edgeTypeName = edgeTypeName;
      this.destinationVertex = destinationVertex;
      this.edgeProperties = edgeProperties;
    }
  }

  public void createVertexType(final VertexType type) {
    for (final Bucket b : type.getBuckets(false)) {
      if (!database.getSchema().existsBucket(b.getName() + OUT_EDGES_SUFFIX))
        database.getSchema().createBucket(b.getName() + OUT_EDGES_SUFFIX);
      if (!database.getSchema().existsBucket(b.getName() + IN_EDGES_SUFFIX))
        database.getSchema().createBucket(b.getName() + IN_EDGES_SUFFIX);
    }
  }

  public void dropVertexType(final VertexType type) {
    for (final Bucket b : type.getBuckets(false)) {
      if (database.getSchema().existsBucket(b.getName() + OUT_EDGES_SUFFIX))
        database.getSchema().dropBucket(b.getName() + OUT_EDGES_SUFFIX);
      if (database.getSchema().existsBucket(b.getName() + IN_EDGES_SUFFIX))
        database.getSchema().dropBucket(b.getName() + IN_EDGES_SUFFIX);
    }
  }

  public ImmutableLightEdge newLightEdge(final VertexInternal fromVertex, final String edgeTypeName, final Identifiable toVertex, final boolean bidirectional) {
    if (toVertex == null)
      throw new IllegalArgumentException("Destination vertex is null");

    final RID fromVertexRID = fromVertex.getIdentity();
    if (fromVertexRID == null)
      throw new IllegalArgumentException("Current vertex is not persistent");

    if (toVertex instanceof MutableDocument && toVertex.getIdentity() == null)
      throw new IllegalArgumentException("Target vertex is not persistent");

    final DatabaseInternal database = (DatabaseInternal) fromVertex.getDatabase();

    final RID edgeRID = new RID(database, database.getSchema().getType(edgeTypeName).getFirstBucketId(), -1l);

    final ImmutableLightEdge edge = new ImmutableLightEdge(database, database.getSchema().getType(edgeTypeName), edgeRID, fromVertexRID,
        toVertex.getIdentity());

    connectEdge(fromVertex, toVertex, edge, bidirectional);

    return edge;
  }

  public MutableEdge newEdge(final VertexInternal fromVertex, String edgeTypeName, final Identifiable toVertex, final boolean bidirectional,
      final Object... edgeProperties) {
    if (toVertex == null)
      throw new IllegalArgumentException("Destination vertex is null");

    final RID fromVertexRID = fromVertex.getIdentity();
    if (fromVertexRID == null)
      throw new IllegalArgumentException("Current vertex is not persistent");

    if (toVertex instanceof MutableDocument && toVertex.getIdentity() == null)
      throw new IllegalArgumentException("Target vertex is not persistent");

    final DatabaseInternal database = (DatabaseInternal) fromVertex.getDatabase();

    final String bucketName;
    if (edgeTypeName.startsWith("bucket:")) {
      bucketName = edgeTypeName.substring("bucket:".length());
      final DocumentType type = database.getSchema().getTypeByBucketName(bucketName);
      if (type == null)
        edgeTypeName = null;
      else
        edgeTypeName = type.getName();
    } else
      bucketName = null;

    final EdgeType type = (EdgeType) database.getSchema().getType(edgeTypeName);

    final MutableEdge edge = new MutableEdge(database, type, fromVertexRID, toVertex.getIdentity());
    if (edgeProperties != null && edgeProperties.length > 0)
      setProperties(edge, edgeProperties);

    if (bucketName != null)
      edge.save(bucketName);
    else
      edge.save();

    connectEdge(fromVertex, toVertex, edge, bidirectional);

    return edge;
  }

  public void connectEdge(VertexInternal fromVertex, final Identifiable toVertex, final Edge edge, final boolean bidirectional) {
    fromVertex = fromVertex.modify();

    final EdgeSegment outChunk = createOutEdgeChunk((MutableVertex) fromVertex);

    final EdgeLinkedList outLinkedList = new EdgeLinkedList(fromVertex, Vertex.DIRECTION.OUT, outChunk);

    outLinkedList.add(edge.getIdentity(), toVertex.getIdentity());

    if (bidirectional)
      connectIncomingEdge(toVertex, fromVertex.getIdentity(), edge.getIdentity());
  }

  public List newEdges(VertexInternal sourceVertex, final List connections, final boolean bidirectional) {

    if (connections == null || connections.isEmpty())
      return Collections.emptyList();

    final RID sourceVertexRID = sourceVertex.getIdentity();

    final List edges = new ArrayList<>(connections.size());
    final List> outEdgePairs = new ArrayList<>();

    for (int i = 0; i < connections.size(); ++i) {
      final CreateEdgeOperation connection = connections.get(i);

      final MutableEdge edge;

      final Identifiable destinationVertex = connection.destinationVertex;

      final EdgeType edgeType = (EdgeType) database.getSchema().getType(connection.edgeTypeName);

      edge = new MutableEdge(database, edgeType, sourceVertexRID, destinationVertex.getIdentity());

      if (connection.edgeProperties != null && connection.edgeProperties.length > 0)
        setProperties(edge, connection.edgeProperties);

      edge.save();

      outEdgePairs.add(new Pair<>(edge, destinationVertex));

      edges.add(edge);
    }

    sourceVertex = sourceVertex.modify();

    final EdgeSegment outChunk = createOutEdgeChunk((MutableVertex) sourceVertex);

    final EdgeLinkedList outLinkedList = new EdgeLinkedList(sourceVertex, Vertex.DIRECTION.OUT, outChunk);
    outLinkedList.addAll(outEdgePairs);

    if (bidirectional) {
      for (int i = 0; i < outEdgePairs.size(); ++i) {
        final Pair edge = outEdgePairs.get(i);
        connectIncomingEdge(edge.getSecond(), edge.getFirst().getIdentity(), sourceVertexRID);
      }
    }

    return edges;
  }

  public void connectIncomingEdge(final Identifiable toVertex, final RID fromVertexRID, final RID edgeRID) {
    final MutableVertex toVertexRecord = toVertex.asVertex().modify();

    final EdgeSegment inChunk = createInEdgeChunk(toVertexRecord);

    final EdgeLinkedList inLinkedList = new EdgeLinkedList(toVertexRecord, Vertex.DIRECTION.IN, inChunk);
    inLinkedList.add(edgeRID, fromVertexRID);
  }

  public EdgeSegment createInEdgeChunk(final MutableVertex toVertex) {
    RID inEdgesHeadChunk = toVertex.getInEdgesHeadChunk();

    EdgeSegment inChunk = null;
    if (inEdgesHeadChunk != null)
      try {
        inChunk = (EdgeSegment) database.lookupByRID(inEdgesHeadChunk, true);
      } catch (final RecordNotFoundException e) {
        LogManager.instance().log(this, Level.WARNING, "Record %s (inEdgesHeadChunk) not found on vertex %s. Creating a new one", inEdgesHeadChunk, toVertex);
        inEdgesHeadChunk = null;
      }

    if (inEdgesHeadChunk == null) {
      inChunk = new MutableEdgeSegment(database, database.getNewEdgeListSize(0));
      database.createRecord(inChunk, getEdgesBucketName(toVertex.getIdentity().getBucketId(), Vertex.DIRECTION.IN));
      inEdgesHeadChunk = inChunk.getIdentity();

      toVertex.setInEdgesHeadChunk(inEdgesHeadChunk);
      database.updateRecord(toVertex);
    }

    return inChunk;
  }

  public EdgeSegment createOutEdgeChunk(final MutableVertex fromVertex) {
    RID outEdgesHeadChunk = fromVertex.getOutEdgesHeadChunk();

    EdgeSegment outChunk = null;
    if (outEdgesHeadChunk != null)
      try {
        outChunk = (EdgeSegment) database.lookupByRID(outEdgesHeadChunk, true);
      } catch (final RecordNotFoundException e) {
        LogManager.instance()
            .log(this, Level.WARNING, "Record %s (outEdgesHeadChunk) not found on vertex %s. Creating a new one", outEdgesHeadChunk, fromVertex.getIdentity());
        outEdgesHeadChunk = null;
      }

    if (outEdgesHeadChunk == null) {
      outChunk = new MutableEdgeSegment(database, database.getNewEdgeListSize(0));
      database.createRecord(outChunk, getEdgesBucketName(fromVertex.getIdentity().getBucketId(), Vertex.DIRECTION.OUT));
      outEdgesHeadChunk = outChunk.getIdentity();

      fromVertex.setOutEdgesHeadChunk(outEdgesHeadChunk);
      database.updateRecord(fromVertex);
    }

    return outChunk;
  }

  public long countEdges(final VertexInternal vertex, final Vertex.DIRECTION direction, final String edgeType) {
    if (direction == null)
      throw new IllegalArgumentException("Direction is null");

    long total = 0;

    switch (direction) {
    case BOTH: {
      final EdgeLinkedList outEdges = getEdgeHeadChunk(vertex, Vertex.DIRECTION.OUT);
      total += (outEdges != null) ? outEdges.count(edgeType) : 0L;

      final EdgeLinkedList inEdges = getEdgeHeadChunk(vertex, Vertex.DIRECTION.IN);
      total += (inEdges != null) ? inEdges.count(edgeType) : 0L;
      break;
    }

    case OUT: {
      final EdgeLinkedList outEdges = getEdgeHeadChunk(vertex, Vertex.DIRECTION.OUT);
      total += (outEdges != null) ? outEdges.count(edgeType) : 0L;
      break;
    }

    case IN: {
      final EdgeLinkedList inEdges = getEdgeHeadChunk(vertex, Vertex.DIRECTION.IN);
      total += (inEdges != null) ? inEdges.count(edgeType) : 0L;
      break;
    }

    default:
      throw new IllegalArgumentException("Invalid direction " + direction);
    }

    return total;
  }

  public void deleteEdge(final Edge edge) {
    final Database database = edge.getDatabase();

    try {
      final VertexInternal vOut = (VertexInternal) edge.getOutVertex();
      if (vOut != null) {
        final EdgeLinkedList outEdges = getEdgeHeadChunk(vOut, Vertex.DIRECTION.OUT);
        if (outEdges != null)
          outEdges.removeEdge(edge);
      }
    } catch (final SchemaException | RecordNotFoundException e) {
      LogManager.instance().log(this, Level.FINE, "Error on loading outgoing vertex %s from edge %s", e, edge.getOut(), edge.getIdentity());
    }

    try {
      final VertexInternal vIn = (VertexInternal) edge.getInVertex();
      if (vIn != null) {
        final EdgeLinkedList inEdges = getEdgeHeadChunk(vIn, Vertex.DIRECTION.IN);
        if (inEdges != null)
          inEdges.removeEdge(edge);
      }
    } catch (final SchemaException | RecordNotFoundException e) {
      LogManager.instance().log(this, Level.FINE, "Error on loading incoming vertex %s from edge %s", e, edge.getIn(), edge.getIdentity());
    }

    final RID edgeRID = edge.getIdentity();
    if (edgeRID != null && !(edge instanceof LightEdge))
      // DELETE EDGE RECORD TOO
      try {
        database.getSchema().getBucketById(edge.getIdentity().getBucketId()).deleteRecord(edge.getIdentity());
      } catch (final RecordNotFoundException e) {
        // ALREADY DELETED: IGNORE IT
      }
  }

  public void deleteVertex(final VertexInternal vertex) {
    final EdgeLinkedList outEdges = getEdgeHeadChunk(vertex, Vertex.DIRECTION.OUT);
    if (outEdges != null) {
      final Iterator outIterator = outEdges.edgeIterator();

      while (outIterator.hasNext()) {
        RID inV = null;
        try {
          final Edge nextEdge = outIterator.next();

          inV = nextEdge.getIn();

          final VertexInternal nextVertex = (VertexInternal) nextEdge.getInVertex();

          final EdgeLinkedList inEdges2 = getEdgeHeadChunk(nextVertex, Vertex.DIRECTION.IN);
          if (inEdges2 != null) {
            inEdges2.removeEdge(nextEdge);

            if (nextEdge.getIdentity().getPosition() > -1)
              // NON LIGHTWEIGHT
              nextEdge.delete();
          }
        } catch (final RecordNotFoundException e) {
          // ALREADY DELETED, IGNORE THIS
          LogManager.instance()
              .log(this, Level.FINE, "Error on deleting outgoing vertex %s connected from vertex %s (record not found)", inV, vertex.getIdentity());
        }
      }

      final RID outRID = vertex.getOutEdgesHeadChunk();
      outRID.getRecord(false).delete();
    }

    final EdgeLinkedList inEdges = getEdgeHeadChunk(vertex, Vertex.DIRECTION.IN);
    if (inEdges != null) {
      final Iterator inIterator = inEdges.edgeIterator();

      while (inIterator.hasNext()) {
        RID outV = null;
        try {
          final Edge nextEdge = inIterator.next();

          outV = nextEdge.getOut();

          final VertexInternal nextVertex = (VertexInternal) nextEdge.getOutVertex();

          final EdgeLinkedList outEdges2 = getEdgeHeadChunk(nextVertex, Vertex.DIRECTION.OUT);
          if (outEdges2 != null) {
            outEdges2.removeEdge(nextEdge);

            if (nextEdge.getIdentity().getPosition() > -1)
              // NON LIGHTWEIGHT
              nextEdge.delete();
          }
        } catch (final RecordNotFoundException e) {
          // ALREADY DELETED, IGNORE THIS
          LogManager.instance().log(this, Level.WARNING, "Error on deleting incoming vertex %s connected to vertex %s", outV, vertex.getIdentity());
        }
      }

      final RID inRID = vertex.getInEdgesHeadChunk();
      inRID.getRecord(false).delete();
    }

    // DELETE VERTEX RECORD
    vertex.getDatabase().getSchema().getBucketById(vertex.getIdentity().getBucketId()).deleteRecord(vertex.getIdentity());
  }

  public Iterable getEdges(final VertexInternal vertex) {
    final MultiIterator result = new MultiIterator<>();

    final EdgeLinkedList outEdges = getEdgeHeadChunk(vertex, Vertex.DIRECTION.OUT);
    if (outEdges != null)
      result.addIterator(outEdges.edgeIterator());

    final EdgeLinkedList inEdges = getEdgeHeadChunk(vertex, Vertex.DIRECTION.IN);
    if (inEdges != null)
      result.addIterator(inEdges.edgeIterator());

    return result;
  }

  public Iterable getEdges(final VertexInternal vertex, final Vertex.DIRECTION direction, final String... edgeTypes) {
    if (direction == null)
      throw new IllegalArgumentException("Direction is null");

    switch (direction) {
    case BOTH: {
      final MultiIterator result = new MultiIterator<>();

      final EdgeLinkedList outEdges = getEdgeHeadChunk(vertex, Vertex.DIRECTION.OUT);
      if (outEdges != null)
        result.addIterator(outEdges.edgeIterator(edgeTypes));

      final EdgeLinkedList inEdges = getEdgeHeadChunk(vertex, Vertex.DIRECTION.IN);
      if (inEdges != null)
        result.addIterator(inEdges.edgeIterator(edgeTypes));
      return result;
    }

    case OUT:
      final EdgeLinkedList outEdges = getEdgeHeadChunk(vertex, Vertex.DIRECTION.OUT);
      if (outEdges != null)
        return () -> outEdges.edgeIterator(edgeTypes);
      break;

    case IN:
      final EdgeLinkedList inEdges = getEdgeHeadChunk(vertex, Vertex.DIRECTION.IN);
      if (inEdges != null)
        return () -> inEdges.edgeIterator(edgeTypes);
      break;

    default:
      throw new IllegalArgumentException("Invalid direction " + direction);
    }
    return Collections.emptyList();
  }

  /**
   * Returns all the connected vertices, both directions, any edge type.
   *
   * @return An iterator of PVertex instances
   */
  public Iterable getVertices(final VertexInternal vertex) {
    final MultiIterator result = new MultiIterator<>();

    final EdgeLinkedList outEdges = getEdgeHeadChunk(vertex, Vertex.DIRECTION.OUT);
    if (outEdges != null)
      result.addIterator(outEdges.vertexIterator());

    final EdgeLinkedList inEdges = getEdgeHeadChunk(vertex, Vertex.DIRECTION.IN);
    if (inEdges != null)
      result.addIterator(inEdges.vertexIterator());

    return result;
  }

  /**
   * Returns the connected vertices.
   *
   * @param direction Direction between OUT, IN or BOTH
   * @param edgeTypes Edge type names to filter
   *
   * @return An iterator of PVertex instances
   */
  public Iterable getVertices(final VertexInternal vertex, final Vertex.DIRECTION direction, final String... edgeTypes) {
    if (direction == null)
      throw new IllegalArgumentException("Direction is null");

    switch (direction) {
    case BOTH: {
      final MultiIterator result = new MultiIterator<>();

      final EdgeLinkedList outEdges = getEdgeHeadChunk(vertex, Vertex.DIRECTION.OUT);
      if (outEdges != null)
        result.addIterator(outEdges.vertexIterator(edgeTypes));

      final EdgeLinkedList inEdges = getEdgeHeadChunk(vertex, Vertex.DIRECTION.IN);
      if (inEdges != null)
        result.addIterator(inEdges.vertexIterator(edgeTypes));
      return result;
    }

    case OUT:
      final EdgeLinkedList outEdges = getEdgeHeadChunk(vertex, Vertex.DIRECTION.OUT);
      if (outEdges != null)
        return () -> outEdges.vertexIterator(edgeTypes);
      break;

    case IN:
      final EdgeLinkedList inEdges = getEdgeHeadChunk(vertex, Vertex.DIRECTION.IN);
      if (inEdges != null)
        return () -> inEdges.vertexIterator(edgeTypes);
      break;

    default:
      throw new IllegalArgumentException("Invalid direction " + direction);
    }
    return Collections.emptyList();
  }

  public boolean isVertexConnectedTo(final VertexInternal vertex, final Identifiable toVertex) {
    if (toVertex == null)
      throw new IllegalArgumentException("Destination vertex is null");

    final EdgeLinkedList outEdges = getEdgeHeadChunk(vertex, Vertex.DIRECTION.OUT);
    if (outEdges != null && outEdges.containsVertex(toVertex.getIdentity(), null))
      return true;

    final EdgeLinkedList inEdges = getEdgeHeadChunk(vertex, Vertex.DIRECTION.IN);
    return inEdges != null && inEdges.containsVertex(toVertex.getIdentity(), null);
  }

  public boolean isVertexConnectedTo(final VertexInternal vertex, final Identifiable toVertex, final Vertex.DIRECTION direction) {
    if (toVertex == null)
      throw new IllegalArgumentException("Destination vertex is null");

    if (direction == null)
      throw new IllegalArgumentException("Direction is null");

    if (direction == Vertex.DIRECTION.OUT || direction == Vertex.DIRECTION.BOTH) {
      final EdgeLinkedList outEdges = getEdgeHeadChunk(vertex, Vertex.DIRECTION.OUT);
      if (outEdges != null && outEdges.containsVertex(toVertex.getIdentity(), null))
        return true;
    }

    if (direction == Vertex.DIRECTION.IN || direction == Vertex.DIRECTION.BOTH) {
      final EdgeLinkedList inEdges = getEdgeHeadChunk(vertex, Vertex.DIRECTION.IN);
      return inEdges != null && inEdges.containsVertex(toVertex.getIdentity(), null);
    }

    return false;
  }

  public boolean isVertexConnectedTo(final VertexInternal vertex, final Identifiable toVertex, final Vertex.DIRECTION direction, final String edgeType) {
    if (toVertex == null)
      throw new IllegalArgumentException("Destination vertex is null");

    if (direction == null)
      throw new IllegalArgumentException("Direction is null");

    if (edgeType == null)
      throw new IllegalArgumentException("Edge type is null");

    final int[] bucketFilter = vertex.getDatabase().getSchema().getType(edgeType).getBuckets(true).stream().mapToInt(x -> x.getId()).toArray();

    if (direction == Vertex.DIRECTION.OUT || direction == Vertex.DIRECTION.BOTH) {
      final EdgeLinkedList outEdges = getEdgeHeadChunk(vertex, Vertex.DIRECTION.OUT);
      if (outEdges != null && outEdges.containsVertex(toVertex.getIdentity(), bucketFilter))
        return true;
    }

    if (direction == Vertex.DIRECTION.IN || direction == Vertex.DIRECTION.BOTH) {
      final EdgeLinkedList inEdges = getEdgeHeadChunk(vertex, Vertex.DIRECTION.IN);
      return inEdges != null && inEdges.containsVertex(toVertex.getIdentity(), bucketFilter);
    }

    return false;
  }

  public String getEdgesBucketName(final int bucketId, final Vertex.DIRECTION direction) {
    final Bucket vertexBucket = database.getSchema().getBucketById(bucketId);

    if (direction == Vertex.DIRECTION.OUT)
      return vertexBucket.getName() + OUT_EDGES_SUFFIX;
    else if (direction == Vertex.DIRECTION.IN)
      return vertexBucket.getName() + IN_EDGES_SUFFIX;

    throw new IllegalArgumentException("Invalid direction");
  }

  public static void setProperties(final MutableDocument edge, final Object[] properties) {
    if (properties != null)
      if (properties.length == 1 && properties[0] instanceof Map) {
        // GET PROPERTIES FROM THE MAP
        final Map map = (Map) properties[0];
        for (final Map.Entry entry : map.entrySet())
          edge.set(entry.getKey(), entry.getValue());
      } else {
        if (properties.length % 2 != 0)
          throw new IllegalArgumentException("Properties must be an even number as pairs of name, value");
        for (int i = 0; i < properties.length; i += 2)
          edge.set((String) properties[i], properties[i + 1]);
      }
  }

  public EdgeLinkedList getEdgeHeadChunk(final VertexInternal vertex, final Vertex.DIRECTION direction) {
    if (direction == Vertex.DIRECTION.OUT) {
      final RID rid = vertex.getOutEdgesHeadChunk();
      if (rid != null) {
        try {
          return new EdgeLinkedList(vertex, Vertex.DIRECTION.OUT, (EdgeSegment) vertex.getDatabase().lookupByRID(rid, true));
        } catch (final RecordNotFoundException e) {
          LogManager.instance().log(this, Level.WARNING, "Cannot load OUT edge list chunk (%s) for vertex %s", e, rid, vertex.getIdentity());
        }
      }
    } else if (direction == Vertex.DIRECTION.IN) {
      final RID rid = vertex.getInEdgesHeadChunk();
      if (rid != null) {
        try {
          return new EdgeLinkedList(vertex, Vertex.DIRECTION.IN, (EdgeSegment) vertex.getDatabase().lookupByRID(rid, true));
        } catch (final RecordNotFoundException e) {
          LogManager.instance().log(this, Level.WARNING, "Cannot load IN edge list chunk (%s) for vertex %s", e, rid, vertex.getIdentity());
        }
      }
    }

    return null;
  }

  public Map checkVertices(final String typeName, final boolean fix, final int verboseLevel) {
    final AtomicLong autoFix = new AtomicLong();
    final AtomicLong invalidLinks = new AtomicLong();
    final LinkedHashSet corruptedRecords = new LinkedHashSet<>();
    final List warnings = new ArrayList<>();

    final Map stats = new HashMap<>();

    database.begin();

    try {
      database.scanType(typeName, false, (record) -> {
        try {
          final Vertex vertex = record.asVertex(true);

          final EdgeLinkedList outEdges = getEdgeHeadChunk((VertexInternal) vertex, Vertex.DIRECTION.OUT);
          if (outEdges != null) {
            final Iterator> out = outEdges.entryIterator();
            while (out.hasNext()) {
              final Pair current = out.next();
              final RID edgeRID = current.getFirst();
              final RID vertexRID = current.getSecond();

              boolean removeEntry = false;

              if (edgeRID == null) {
                warnings.add("outgoing edge null from vertex " + vertex.getIdentity());
                removeEntry = true;
                invalidLinks.incrementAndGet();
              } else if (vertexRID == null) {
                warnings.add("outgoing vertex null from vertex " + vertex.getIdentity());
                corruptedRecords.add(edgeRID);
                removeEntry = true;
                invalidLinks.incrementAndGet();
              } else {
                try {
                  if (edgeRID.getPosition() < 0)
                    // LIGHTWEIGHT EDGE
                    continue;

                  final Edge edge = edgeRID.asEdge(true);

                  if (edge.getIn() == null || !edge.getIn().isValid()) {
                    warnings.add("edge " + edgeRID + " has an invalid incoming link " + edge.getIn());
                    corruptedRecords.add(edgeRID);
                    removeEntry = true;
                    invalidLinks.incrementAndGet();
                  } else {
                    try {
                      edge.getInVertex().asVertex(true);
                    } catch (final RecordNotFoundException e) {
                      warnings.add("edge " + edgeRID + " points to the incoming vertex " + edge.getIn() + " that is not found (deleted?)");
                      corruptedRecords.add(edgeRID);
                      removeEntry = true;
                      corruptedRecords.add(edge.getIn());
                      invalidLinks.incrementAndGet();
                    } catch (final Exception e) {
                      // UNKNOWN ERROR ON LOADING
                      warnings.add(
                          "edge " + edgeRID + " points to the incoming vertex " + edge.getIn() + " which cannot be loaded (error: " + e.getMessage() + ")");
                      corruptedRecords.add(edgeRID);
                      removeEntry = true;
                      corruptedRecords.add(edge.getIn());
                    }
                  }

                  if (!edge.getOut().equals(vertex.getIdentity())) {
                    warnings.add("edge " + edgeRID + " has an outgoing link " + edge.getOut() + " different from expected " + vertex.getIdentity());
                    corruptedRecords.add(edgeRID);
                    removeEntry = true;
                    invalidLinks.incrementAndGet();
                  } else if (!edge.getIn().equals(vertexRID)) {
                    warnings.add("edge " + edgeRID + " has an incoming link " + edge.getIn() + " different from expected " + vertexRID);
                    corruptedRecords.add(edgeRID);
                    removeEntry = true;
                    invalidLinks.incrementAndGet();
                  }

                } catch (final RecordNotFoundException e) {
                  warnings.add("edge " + edgeRID + " not found");
                  corruptedRecords.add(edgeRID);
                  removeEntry = true;
                  invalidLinks.incrementAndGet();
                } catch (final Exception e) {
                  // UNKNOWN ERROR ON LOADING
                  warnings.add("edge " + edgeRID + " error on loading (error: " + e.getMessage() + ")");
                  corruptedRecords.add(edgeRID);
                  removeEntry = true;
                }
              }

              if (fix && removeEntry)
                out.remove();
            }
          }

          final EdgeLinkedList inEdges = getEdgeHeadChunk((VertexInternal) vertex, Vertex.DIRECTION.IN);
          if (inEdges != null) {
            final Iterator> in = inEdges.entryIterator();
            while (in.hasNext()) {
              final Pair current = in.next();
              final RID edgeRID = current.getFirst();
              final RID vertexRID = current.getSecond();

              boolean removeEntry = false;

              if (edgeRID == null) {
                warnings.add("outgoing edge null from vertex " + vertex.getIdentity());
                removeEntry = true;
                invalidLinks.incrementAndGet();
              } else if (vertexRID == null) {
                warnings.add("outgoing vertex null from vertex " + vertex.getIdentity());
                corruptedRecords.add(edgeRID);
                removeEntry = true;
                invalidLinks.incrementAndGet();
              } else {
                if (edgeRID.getPosition() < 0)
                  // LIGHTWEIGHT EDGE
                  continue;

                try {
                  final Edge edge = edgeRID.asEdge(true);

                  if (edge.getOut() == null || !edge.getOut().isValid()) {
                    warnings.add("edge " + edgeRID + " has an invalid outgoing link " + edge.getIn());
                    corruptedRecords.add(edgeRID);
                    removeEntry = true;
                    invalidLinks.incrementAndGet();
                  } else {
                    try {
                      edge.getOutVertex().asVertex(true);
                    } catch (final RecordNotFoundException e) {
                      warnings.add("edge " + edgeRID + " points to the outgoing vertex " + edge.getOut() + " that is not found (deleted?)");
                      corruptedRecords.add(edgeRID);
                      removeEntry = true;
                      corruptedRecords.add(edge.getOut());
                      invalidLinks.incrementAndGet();
                    } catch (final Exception e) {
                      // UNKNOWN ERROR ON LOADING
                      warnings.add(
                          "edge " + edgeRID + " points to the outgoing vertex " + edge.getOut() + " which cannot be loaded (error: " + e.getMessage() + ")");
                      corruptedRecords.add(edgeRID);
                      removeEntry = true;
                      corruptedRecords.add(edge.getOut());
                    }
                  }

                  if (!edge.getIn().equals(vertex.getIdentity())) {
                    warnings.add("edge " + edgeRID + " has an incoming link " + edge.getIn() + " different from expected " + vertex.getIdentity());
                    corruptedRecords.add(edgeRID);
                    removeEntry = true;
                    invalidLinks.incrementAndGet();
                  } else if (!edge.getOut().equals(vertexRID)) {
                    warnings.add("edge " + edgeRID + " has an outgoing link " + edge.getOut() + " different from expected " + vertexRID);
                    corruptedRecords.add(edgeRID);
                    removeEntry = true;
                    invalidLinks.incrementAndGet();
                  }
                } catch (final RecordNotFoundException e) {
                  warnings.add("edge " + edgeRID + " not found");
                  corruptedRecords.add(edgeRID);
                  removeEntry = true;
                  invalidLinks.incrementAndGet();
                } catch (final Exception e) {
                  // UNKNOWN ERROR ON LOADING
                  warnings.add("edge " + edgeRID + " error on loading (error: " + e.getMessage() + ")");
                  corruptedRecords.add(edgeRID);
                  removeEntry = true;
                }
              }

              if (fix && removeEntry)
                in.remove();
            }
          }

        } catch (final Throwable e) {
          warnings.add("vertex " + record.getIdentity() + " cannot be loaded (error: " + e.getMessage() + ")");
          corruptedRecords.add(record.getIdentity());
        }

        return true;
      }, (rid, exception) -> {
        warnings.add("vertex " + rid + " cannot be loaded (error: " + exception.getMessage() + ")");
        corruptedRecords.add(rid);
        return true;
      });

      if (fix) {
        for (final RID rid : corruptedRecords) {
          autoFix.incrementAndGet();
          try {
            database.getSchema().getBucketById(rid.getBucketId()).deleteRecord(rid);
          } catch (final RecordNotFoundException e) {
            // IGNORE IT
          } catch (final Throwable e) {
            warnings.add("Cannot fix the record " + rid + ": error on delete (error: " + e.getMessage() + ")");
          }
        }
      }

      if (verboseLevel > 0)
        for (final String warning : warnings)
          LogManager.instance().log(this, Level.WARNING, "- " + warning);

      database.commit();

    } finally {
      stats.put("autoFix", autoFix.get());
      stats.put("corruptedRecords", corruptedRecords);
      stats.put("invalidLinks", invalidLinks.get());
      stats.put("warnings", warnings);
    }

    return stats;
  }

  public Map checkEdges(final String typeName, final boolean fix, final int verboseLevel) {
    final AtomicLong autoFix = new AtomicLong();
    final AtomicLong invalidLinks = new AtomicLong();
    final AtomicLong missingReferenceBack = new AtomicLong();
    final List corruptedRecords = new ArrayList<>();
    final List warnings = new ArrayList<>();

    final Map stats = new HashMap<>();

    database.begin();

    try {
      database.scanType(typeName, false, (record) -> {
        final RID edgeRID = record.getIdentity();

        try {
          final Edge edge = record.asEdge(true);

          if (edge == null) {
            warnings.add("edge " + edgeRID + " cannot be loaded");
            corruptedRecords.add(edgeRID);

          } else if (edge.getIn() == null || !edge.getIn().isValid()) {
            warnings.add("edge " + edgeRID + " has an invalid incoming link " + edge.getIn());
            corruptedRecords.add(edgeRID);
            invalidLinks.incrementAndGet();

          } else if (edge.getOut() == null || !edge.getOut().isValid()) {
            warnings.add("edge " + edgeRID + " has an invalid outgoing link " + edge.getOut());
            corruptedRecords.add(edgeRID);
            invalidLinks.incrementAndGet();

          } else {
            try {
              final Vertex vertex = edge.getInVertex().asVertex(true);

              final EdgeLinkedList inEdges = getEdgeHeadChunk((VertexInternal) vertex, Vertex.DIRECTION.IN);
              if (inEdges == null || !inEdges.containsEdge(edgeRID))
                // UNI DIRECTIONAL EDGE
                missingReferenceBack.incrementAndGet();

            } catch (final RecordNotFoundException e) {
              warnings.add("edge " + edgeRID + " points to the incoming vertex " + edge.getIn() + " that is not found (deleted?)");
              corruptedRecords.add(edgeRID);
              corruptedRecords.add(edge.getIn());
              invalidLinks.incrementAndGet();
            } catch (final Exception e) {
              // UNKNOWN ERROR ON LOADING
              warnings.add("edge " + edgeRID + " points to the incoming vertex " + edge.getIn() + " which cannot be loaded (error: " + e.getMessage() + ")");
              corruptedRecords.add(edgeRID);
              corruptedRecords.add(edge.getIn());
            }

            try {
              final Vertex vertex = edge.getOutVertex().asVertex(true);

              final EdgeLinkedList outEdges = getEdgeHeadChunk((VertexInternal) vertex, Vertex.DIRECTION.OUT);
              if (outEdges == null || !outEdges.containsEdge(edgeRID))
                // UNI DIRECTIONAL EDGE
                missingReferenceBack.incrementAndGet();

            } catch (final RecordNotFoundException e) {
              warnings.add("edge " + edgeRID + " points to the outgoing vertex " + edge.getOut() + " that is not found (deleted?)");
              corruptedRecords.add(edgeRID);
              invalidLinks.incrementAndGet();
            } catch (final Exception e) {
              // UNKNOWN ERROR ON LOADING
              warnings.add("edge " + edgeRID + " points to the outgoing vertex " + edge.getOut() + " which cannot be loaded (error: " + e.getMessage() + ")");
              corruptedRecords.add(edgeRID);
              corruptedRecords.add(edge.getOut());
            }
          }

        } catch (final Throwable e) {
          warnings.add("edge " + record.getIdentity() + " cannot be loaded (error: " + e.getMessage() + ")");
          corruptedRecords.add(edgeRID);
        }

        return true;
      }, (rid, exception) -> {
        warnings.add("edge " + rid + " cannot be loaded (error: " + exception.getMessage() + ")");
        corruptedRecords.add(rid);
        return true;
      });

      if (fix) {
        for (final RID rid : corruptedRecords) {
          autoFix.incrementAndGet();
          try {
            database.getSchema().getBucketById(rid.getBucketId()).deleteRecord(rid);
          } catch (final RecordNotFoundException e) {
            // IGNORE IT
          } catch (final Throwable e) {
            warnings.add("Cannot fix the record " + rid + ": error on delete (error: " + e.getMessage() + ")");
          }
        }
      }

      if (verboseLevel > 0)
        for (final String warning : warnings)
          LogManager.instance().log(this, Level.WARNING, "- " + warning);

      database.commit();

    } finally {
      stats.put("autoFix", autoFix.get());
      stats.put("corruptedRecords", corruptedRecords);
      stats.put("invalidLinks", invalidLinks.get());
      stats.put("missingReferenceBack", missingReferenceBack.get());
      stats.put("warnings", warnings);
    }

    return stats;
  }

}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy