Package com.orientechnologies.orient.core.db.tool

Source Code of com.orientechnologies.orient.core.db.tool.ODatabaseCompare

/*
  *
  *  *  Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com)
  *  *
  *  *  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.
  *  *
  *  * For more information: http://www.orientechnologies.com
  *
  */
package com.orientechnologies.orient.core.db.tool;

import static com.orientechnologies.orient.core.record.impl.ODocumentHelper.makeDbCall;

import java.io.IOException;
import java.util.Arrays;
import java.util.Collection;
import java.util.Iterator;
import java.util.Set;

import com.orientechnologies.common.log.OLogManager;
import com.orientechnologies.orient.core.Orient;
import com.orientechnologies.orient.core.command.OCommandOutputListener;
import com.orientechnologies.orient.core.db.ODatabaseRecordThreadLocal;
import com.orientechnologies.orient.core.db.document.ODatabaseDocumentTx;
import com.orientechnologies.orient.core.db.record.OIdentifiable;
import com.orientechnologies.orient.core.id.OClusterPosition;
import com.orientechnologies.orient.core.id.OClusterPositionFactory;
import com.orientechnologies.orient.core.id.ORID;
import com.orientechnologies.orient.core.id.ORecordId;
import com.orientechnologies.orient.core.index.OIndex;
import com.orientechnologies.orient.core.index.OIndexKeyCursor;
import com.orientechnologies.orient.core.index.OIndexManager;
import com.orientechnologies.orient.core.metadata.OMetadataDefault;
import com.orientechnologies.orient.core.metadata.schema.OClass;
import com.orientechnologies.orient.core.metadata.schema.OProperty;
import com.orientechnologies.orient.core.metadata.schema.OSchema;
import com.orientechnologies.orient.core.metadata.schema.OType;
import com.orientechnologies.orient.core.record.impl.ODocument;
import com.orientechnologies.orient.core.record.impl.ODocumentHelper;
import com.orientechnologies.orient.core.record.impl.ODocumentHelper.ODbRelatedCall;
import com.orientechnologies.orient.core.record.impl.ORecordFlat;
import com.orientechnologies.orient.core.storage.OPhysicalPosition;
import com.orientechnologies.orient.core.storage.ORawBuffer;
import com.orientechnologies.orient.core.storage.OStorage;

public class ODatabaseCompare extends ODatabaseImpExpAbstract {
  private OStorage              storage1;
  private OStorage              storage2;

  private ODatabaseDocumentTx   databaseDocumentTxOne;
  private ODatabaseDocumentTx   databaseDocumentTxTwo;

  private boolean               compareEntriesForAutomaticIndexes = false;
  private boolean               autoDetectExportImportMap         = true;

  private OIndex<OIdentifiable> exportImportHashTable             = null;
  private int                   differences                       = 0;
  private boolean               compareIndexMetadata              = false;

  public ODatabaseCompare(String iDb1URL, String iDb2URL, final OCommandOutputListener iListener) throws IOException {
    super(null, null, iListener);

    listener.onMessage("\nComparing two local databases:\n1) " + iDb1URL + "\n2) " + iDb2URL + "\n");

    storage1 = Orient.instance().loadStorage(iDb1URL);
    storage1.open(null, null, null);

    storage2 = Orient.instance().loadStorage(iDb2URL);
    storage2.open(null, null, null);
  }

  public ODatabaseCompare(String iDb1URL, String iDb2URL, final String userName, final String userPassword,
      final OCommandOutputListener iListener) throws IOException {
    super(null, null, iListener);

    listener.onMessage("\nComparing two local databases:\n1) " + iDb1URL + "\n2) " + iDb2URL + "\n");

    databaseDocumentTxOne = new ODatabaseDocumentTx(iDb1URL);
    databaseDocumentTxOne.open(userName, userPassword);

    databaseDocumentTxTwo = new ODatabaseDocumentTx(iDb2URL);
    databaseDocumentTxTwo.open(userName, userPassword);

    storage1 = databaseDocumentTxOne.getStorage();

    storage2 = databaseDocumentTxTwo.getStorage();

    // exclude automatically generated clusters
    excludeClusters.add("orids");
    excludeClusters.add(OMetadataDefault.CLUSTER_INDEX_NAME);
    excludeClusters.add(OMetadataDefault.CLUSTER_MANUAL_INDEX_NAME);
  }

  public void setCompareIndexMetadata(boolean compareIndexMetadata) {
    this.compareIndexMetadata = compareIndexMetadata;
  }

  public boolean isCompareEntriesForAutomaticIndexes() {
    return compareEntriesForAutomaticIndexes;
  }

  public void setCompareEntriesForAutomaticIndexes(boolean compareEntriesForAutomaticIndexes) {
    this.compareEntriesForAutomaticIndexes = compareEntriesForAutomaticIndexes;
  }

  public void setAutoDetectExportImportMap(boolean autoDetectExportImportMap) {
    this.autoDetectExportImportMap = autoDetectExportImportMap;
  }

  public boolean compare() {
    if (isDocumentDatabases() && (databaseDocumentTxOne == null || databaseDocumentTxTwo == null)) {
      listener.onMessage("\nPassed in URLs are related to document databases but credentials "
          + "were not provided to open them. Please provide user name + password for databases to compare");
      return false;
    }

    if (!isDocumentDatabases() && (databaseDocumentTxOne != null || databaseDocumentTxTwo != null)) {
      listener.onMessage("\nPassed in URLs are not related to document databases but credentials "
          + "were provided to open them. Please do not provide user name + password for databases to compare");
      return false;
    }

    try {
      ODocumentHelper.RIDMapper ridMapper = null;
      if (autoDetectExportImportMap) {
        listener
            .onMessage("\nAuto discovery of mapping between RIDs of exported and imported records is switched on, try to discover mapping data on disk.");
        exportImportHashTable = (OIndex<OIdentifiable>) databaseDocumentTxTwo.getMetadata().getIndexManager()
            .getIndex(ODatabaseImport.EXPORT_IMPORT_MAP_NAME);
        if (exportImportHashTable != null) {
          listener.onMessage("\nMapping data were found and will be loaded.");
          ridMapper = new ODocumentHelper.RIDMapper() {
            @Override
            public ORID map(ORID rid) {
              if (rid == null)
                return null;

              if (!rid.isPersistent())
                return null;

              final OIdentifiable result = exportImportHashTable.get(rid);
              if (result == null)
                return null;

              return result.getIdentity();
            }
          };
        } else
          listener.onMessage("\nMapping data were not found.");
      }

      compareClusters();
      compareRecords(ridMapper);

      if (isDocumentDatabases()) {
        compareSchama();
        compareIndexes(ridMapper);
      }

      if (differences == 0) {
        listener.onMessage("\n\nDatabases match.");
        return true;
      } else {
        listener.onMessage("\n\nDatabases do not match. Found " + differences + " difference(s).");
        return false;
      }
    } catch (Exception e) {
      OLogManager.instance()
          .error(this, "Error on comparing database '%s' against '%s'", e, storage1.getName(), storage2.getName());
      throw new ODatabaseExportException("Error on comparing database '" + storage1.getName() + "' against '" + storage2.getName()
          + "'", e);
    } finally {
      storage1.close();
      storage2.close();
    }
  }

  private void compareSchama() {
    OSchema schema1 = databaseDocumentTxOne.getMetadata().getSchema();
    OSchema schema2 = databaseDocumentTxTwo.getMetadata().getSchema();
    boolean ok = true;
    for (OClass clazz : schema1.getClasses()) {
      OClass clazz2 = schema2.getClass(clazz.getName());
      if (clazz2 == null) {
        listener.onMessage("\n- ERR: Class definition " + clazz.getName() + " for DB2 is null.");
        continue;
      }
      if (clazz.getSuperClass() != null) {
        if (!clazz.getSuperClass().getName().equals(clazz2.getSuperClass().getName())) {
          listener.onMessage("\n- ERR: Class definition for " + clazz.getName() + " as not same superclass in DB2.");
          ok = false;
        }
      }
      if (!clazz.getClassIndexes().equals(clazz2.getClassIndexes())) {
        listener.onMessage("\n- ERR: Class definition for " + clazz.getName() + " as not same defined indexes in DB2.");
        ok = false;
      }
      if (!Arrays.equals(clazz.getClusterIds(), clazz2.getClusterIds())) {
        listener.onMessage("\n- ERR: Class definition for " + clazz.getName() + " as not same defined clusters in DB2.");
        ok = false;
      }
      if (!clazz.getCustomKeys().equals(clazz2.getCustomKeys())) {
        listener.onMessage("\n- ERR: Class definition for " + clazz.getName() + " as not same defined custom keys in DB2.");
        ok = false;
      }
      if ((clazz.getJavaClass() == null && clazz2.getJavaClass() != null)
          || (clazz.getJavaClass() != null && !clazz.getJavaClass().equals(clazz2.getJavaClass()))) {
        listener.onMessage("\n- ERR: Class definition for " + clazz.getName() + " as not same defined Java class in DB2.");
        ok = false;
      }
      if (clazz.getOverSize() != clazz2.getOverSize()) {
        listener.onMessage("\n- ERR: Class definition for " + clazz.getName() + " as not same defined overSize in DB2.");
        ok = false;
      }

      if (clazz.getDefaultClusterId() != clazz2.getDefaultClusterId()) {
        listener.onMessage("\n- ERR: Class definition for " + clazz.getName() + " as not same defined default cluser id in DB2.");
        ok = false;
      }

      if (clazz.getSize() != clazz2.getSize()) {
        listener.onMessage("\n- ERR: Class definition for " + clazz.getName() + " as not same defined size in DB2.");
        ok = false;
      }
      for (OProperty prop : clazz.declaredProperties()) {
        OProperty prop2 = clazz2.getProperty(prop.getName());
        if (prop2 == null) {
          listener.onMessage("\n- ERR: Class definition for " + clazz.getName() + " as missed property " + prop.getName()
              + "in DB2.");
          ok = false;
        }
        if (prop.getType() != prop2.getType()) {
          listener.onMessage("\n- ERR: Class definition for " + clazz.getName() + " as not same type for property "
              + prop.getName() + "in DB2. ");
          ok = false;
        }

        if (prop.getLinkedType() != prop2.getLinkedType()) {
          listener.onMessage("\n- ERR: Class definition for " + clazz.getName() + " as not same linkedtype for property "
              + prop.getName() + "in DB2.");
          ok = false;
        }

        if (prop.getMin() != null) {
          if (!prop.getMin().equals(prop2.getMin())) {
            listener.onMessage("\n- ERR: Class definition for " + clazz.getName() + " as not same min for property "
                + prop.getName() + "in DB2.");
            ok = false;
          }
        }
        if (prop.getMax() != null) {
          if (!prop.getMax().equals(prop2.getMax())) {
            listener.onMessage("\n- ERR: Class definition for " + clazz.getName() + " as not same max for property "
                + prop.getName() + "in DB2.");
            ok = false;
          }
        }

        if (prop.getMax() != null) {
          if (!prop.getMax().equals(prop2.getMax())) {
            listener.onMessage("\n- ERR: Class definition for " + clazz.getName() + " as not same regexp for property "
                + prop.getName() + "in DB2.");
            ok = false;
          }
        }

        if (prop.getLinkedClass() != null) {
          if (!prop.getLinkedClass().equals(prop2.getLinkedClass())) {
            listener.onMessage("\n- ERR: Class definition for " + clazz.getName() + " as not same linked class for property "
                + prop.getName() + "in DB2.");
            ok = false;
          }
        }

        if (prop.getLinkedClass() != null) {
          if (!prop.getCustomKeys().equals(prop2.getCustomKeys())) {
            listener.onMessage("\n- ERR: Class definition for " + clazz.getName() + " as not same custom keys for property "
                + prop.getName() + "in DB2.");
            ok = false;
          }
        }
        if (prop.isMandatory() != prop2.isMandatory()) {
          listener.onMessage("\n- ERR: Class definition for " + clazz.getName() + " as not same mandatory flag for property "
              + prop.getName() + "in DB2.");
          ok = false;
        }
        if (prop.isNotNull() != prop2.isNotNull()) {
          listener.onMessage("\n- ERR: Class definition for " + clazz.getName() + " as not same nut null flag for property "
              + prop.getName() + "in DB2.");
          ok = false;
        }
        if (prop.isReadonly() != prop2.isReadonly()) {
          listener.onMessage("\n- ERR: Class definition for " + clazz.getName()
              + " as not same readonly flag setting for property " + prop.getName() + "in DB2.");
          ok = false;
        }

      }
      if (!ok) {
        ++differences;
        ok = true;
      }
    }
  }

  @SuppressWarnings({ "unchecked", "rawtypes" })
  private void compareIndexes(ODocumentHelper.RIDMapper ridMapper) {
    listener.onMessage("\nStarting index comparison:");

    boolean ok = true;

    final OIndexManager indexManagerOne = makeDbCall(databaseDocumentTxOne, new ODbRelatedCall<OIndexManager>() {
      public OIndexManager call() {
        return databaseDocumentTxOne.getMetadata().getIndexManager();
      }
    });

    final OIndexManager indexManagerTwo = makeDbCall(databaseDocumentTxTwo, new ODbRelatedCall<OIndexManager>() {
      public OIndexManager call() {
        return databaseDocumentTxTwo.getMetadata().getIndexManager();
      }
    });

    final Collection<? extends OIndex<?>> indexesOne = makeDbCall(databaseDocumentTxOne,
        new ODbRelatedCall<Collection<? extends OIndex<?>>>() {
          public Collection<? extends OIndex<?>> call() {
            return indexManagerOne.getIndexes();
          }
        });

    int indexesSizeOne = makeDbCall(databaseDocumentTxTwo, new ODbRelatedCall<Integer>() {
      public Integer call() {
        return indexesOne.size();
      }
    });

    int indexesSizeTwo = makeDbCall(databaseDocumentTxTwo, new ODbRelatedCall<Integer>() {
      public Integer call() {
        return indexManagerTwo.getIndexes().size();
      }
    });

    if (exportImportHashTable != null)
      indexesSizeTwo--;

    if (indexesSizeOne != indexesSizeTwo) {
      ok = false;
      listener.onMessage("\n- ERR: Amount of indexes are different.");
      listener.onMessage("\n--- DB1: " + indexesSizeOne);
      listener.onMessage("\n--- DB2: " + indexesSizeTwo);
      listener.onMessage("\n");
      ++differences;
    }

    final Iterator<? extends OIndex<?>> iteratorOne = makeDbCall(databaseDocumentTxOne,
        new ODbRelatedCall<Iterator<? extends OIndex<?>>>() {
          public Iterator<? extends OIndex<?>> call() {
            return indexesOne.iterator();
          }
        });

    while (makeDbCall(databaseDocumentTxOne, new ODbRelatedCall<Boolean>() {
      public Boolean call() {
        return iteratorOne.hasNext();
      }
    })) {
      final OIndex indexOne = makeDbCall(databaseDocumentTxOne, new ODbRelatedCall<OIndex<?>>() {
        public OIndex<?> call() {
          return iteratorOne.next();
        }
      });

      final OIndex<?> indexTwo = makeDbCall(databaseDocumentTxTwo, new ODbRelatedCall<OIndex<?>>() {
        public OIndex<?> call() {
          return indexManagerTwo.getIndex(indexOne.getName());
        }
      });

      if (indexTwo == null) {
        ok = false;
        listener.onMessage("\n- ERR: Index " + indexOne.getName() + " is absent in DB2.");
        ++differences;
        continue;
      }

      if (!indexOne.getType().equals(indexTwo.getType())) {
        ok = false;
        listener.onMessage("\n- ERR: Index types for index " + indexOne.getName() + " are different.");
        listener.onMessage("\n--- DB1: " + indexOne.getType());
        listener.onMessage("\n--- DB2: " + indexTwo.getType());
        listener.onMessage("\n");
        ++differences;
        continue;
      }

      if (!indexOne.getClusters().equals(indexTwo.getClusters())) {
        ok = false;
        listener.onMessage("\n- ERR: Clusters to index for index " + indexOne.getName() + " are different.");
        listener.onMessage("\n--- DB1: " + indexOne.getClusters());
        listener.onMessage("\n--- DB2: " + indexTwo.getClusters());
        listener.onMessage("\n");
        ++differences;
        continue;
      }

      if (indexOne.getDefinition() == null && indexTwo.getDefinition() != null) {
        ok = false;
        listener.onMessage("\n- ERR: Index definition for index " + indexOne.getName() + " for DB2 is not null.");
        ++differences;
        continue;
      } else if (indexOne.getDefinition() != null && indexTwo.getDefinition() == null) {
        ok = false;
        listener.onMessage("\n- ERR: Index definition for index " + indexOne.getName() + " for DB2 is null.");
        ++differences;
        continue;
      } else if (indexOne.getDefinition() != null && !indexOne.getDefinition().equals(indexTwo.getDefinition())) {
        ok = false;
        listener.onMessage("\n- ERR: Index definitions for index " + indexOne.getName() + " are different.");
        listener.onMessage("\n--- DB1: " + indexOne.getDefinition());
        listener.onMessage("\n--- DB2: " + indexTwo.getDefinition());
        listener.onMessage("\n");
        ++differences;
        continue;
      }

      final long indexOneSize = makeDbCall(databaseDocumentTxOne, new ODbRelatedCall<Long>() {
        public Long call() {
          return indexOne.getSize();
        }
      });

      final long indexTwoSize = makeDbCall(databaseDocumentTxTwo, new ODbRelatedCall<Long>() {
        public Long call() {
          return indexTwo.getSize();
        }
      });

      if (indexOneSize != indexTwoSize) {
        ok = false;
        listener.onMessage("\n- ERR: Amount of entries for index " + indexOne.getName() + " are different.");
        listener.onMessage("\n--- DB1: " + indexOneSize);
        listener.onMessage("\n--- DB2: " + indexTwoSize);
        listener.onMessage("\n");
        ++differences;
      }

      if (compareIndexMetadata) {
        final ODocument metadataOne = indexOne.getMetadata();
        final ODocument metadataTwo = indexTwo.getMetadata();

        if (metadataOne == null && metadataTwo != null) {
          ok = false;
          listener.onMessage("\n- ERR: Metadata for index " + indexOne.getName() + " for DB1 is null but for DB2 is not.");
          listener.onMessage("\n");
          ++differences;
        } else if (metadataOne != null && metadataTwo == null) {
          ok = false;
          listener.onMessage("\n- ERR: Metadata for index " + indexOne.getName() + " for DB1 is not null but for DB2 is null.");
          listener.onMessage("\n");
          ++differences;
        } else if (metadataOne != null && metadataTwo != null
            && !ODocumentHelper.hasSameContentOf(metadataOne, databaseDocumentTxOne, metadataTwo, databaseDocumentTxTwo, ridMapper)) {
          ok = false;
          listener.onMessage("\n- ERR: Metadata for index " + indexOne.getName() + " for DB1 and for DB2 are different.");
          makeDbCall(databaseDocumentTxOne, new ODbRelatedCall<Object>() {
            @Override
            public Object call() {
              listener.onMessage("\n--- M1: " + metadataOne);
              return null;
            }
          });
          makeDbCall(databaseDocumentTxTwo, new ODbRelatedCall<Object>() {
            @Override
            public Object call() {
              listener.onMessage("\n--- M2: " + metadataTwo);
              return null;
            }
          });
          listener.onMessage("\n");
          ++differences;
        }
      }

      if (((compareEntriesForAutomaticIndexes && !indexOne.getType().equals("DICTIONARY")) || !indexOne.isAutomatic())) {
        final OIndexKeyCursor indexKeyCursorOne = makeDbCall(databaseDocumentTxOne, new ODbRelatedCall<OIndexKeyCursor>() {
          public OIndexKeyCursor call() {
            return indexOne.keyCursor();
          }
        });

        Object key = makeDbCall(databaseDocumentTxOne, new ODbRelatedCall<Object>() {
          @Override
          public Object call() {
            return indexKeyCursorOne.next(-1);
          }
        });

        while (key != null) {
          final Object indexKey = key;

          Object indexOneValue = makeDbCall(databaseDocumentTxOne, new ODbRelatedCall<Object>() {
            public Object call() {
              return indexOne.get(indexKey);
            }
          });

          final Object indexTwoValue = makeDbCall(databaseDocumentTxTwo, new ODbRelatedCall<Object>() {
            public Object call() {
              return indexTwo.get(indexKey);
            }
          });

          if (indexTwoValue == null) {
            ok = false;
            listener.onMessage("\n- ERR: Entry with key " + key + " is absent in index " + indexOne.getName() + " for DB2.");
            ++differences;
          } else if (indexOneValue instanceof Set && indexTwoValue instanceof Set) {
            final Set<Object> indexOneValueSet = (Set<Object>) indexOneValue;
            final Set<Object> indexTwoValueSet = (Set<Object>) indexTwoValue;

            if (!ODocumentHelper.compareSets(databaseDocumentTxOne, indexOneValueSet, databaseDocumentTxTwo, indexTwoValueSet,
                ridMapper)) {
              ok = false;
              reportIndexDiff(indexOne, key, indexOneValue, indexTwoValue);
            }
          } else if (indexOneValue instanceof ORID && indexTwoValue instanceof ORID) {
            if (ridMapper != null && ((ORID) indexOneValue).isPersistent()) {
              OIdentifiable identifiable = ridMapper.map((ORID) indexOneValue);

              if (identifiable != null)
                indexOneValue = identifiable.getIdentity();
            }
            if (!indexOneValue.equals(indexTwoValue)) {
              ok = false;
              reportIndexDiff(indexOne, key, indexOneValue, indexTwoValue);
            }
          } else if (!indexOneValue.equals(indexTwoValue)) {
            ok = false;
            reportIndexDiff(indexOne, key, indexOneValue, indexTwoValue);
          }

          key = makeDbCall(databaseDocumentTxOne, new ODbRelatedCall<Object>() {
            @Override
            public Object call() {
              return indexKeyCursorOne.next(-1);
            }
          });
        }
      }
    }

    if (ok)
      listener.onMessage("OK");
  }

  private boolean compareClusters() {
    listener.onMessage("\nStarting shallow comparison of clusters:");

    listener.onMessage("\nChecking the number of clusters...");

    if (storage1.getClusterNames().size() != storage1.getClusterNames().size()) {
      listener.onMessage("ERR: cluster sizes are different: " + storage1.getClusterNames().size() + " <-> "
          + storage1.getClusterNames().size());
      ++differences;
    }

    int cluster2Id;
    boolean ok;

    for (String clusterName : storage1.getClusterNames()) {
      // CHECK IF THE CLUSTER IS INCLUDED
      if (includeClusters != null) {
        if (!includeClusters.contains(clusterName))
          continue;
      } else if (excludeClusters != null) {
        if (excludeClusters.contains(clusterName))
          continue;
      }

      ok = true;
      cluster2Id = storage2.getClusterIdByName(clusterName);

      listener.onMessage("\n- Checking cluster " + String.format("%-25s: ", "'" + clusterName + "'"));

      if (cluster2Id == -1) {
        listener.onMessage("ERR: cluster name " + clusterName + " was not found on database " + storage2);
        ++differences;
        ok = false;
      }

      if (cluster2Id != storage1.getClusterIdByName(clusterName)) {
        listener.onMessage("ERR: cluster id is different for cluster " + clusterName + ": "
            + storage1.getClusterIdByName(clusterName) + " <-> " + cluster2Id);
        ++differences;
        ok = false;
      }

      if (storage1.count(cluster2Id) != storage2.count(cluster2Id)) {
        listener.onMessage("ERR: number of records different in cluster '" + clusterName + "' (id=" + cluster2Id + "): "
            + storage1.count(cluster2Id) + " <-> " + storage2.count(cluster2Id));
        ++differences;
        ok = false;
      }

      if (ok)
        listener.onMessage("OK");
    }

    listener.onMessage("\n\nShallow analysis done.");
    return true;
  }

  private boolean compareRecords(ODocumentHelper.RIDMapper ridMapper) {
    listener.onMessage("\nStarting deep comparison record by record. This may take a few minutes. Wait please...");

    int clusterId;

    for (String clusterName : storage1.getClusterNames()) {
      // CHECK IF THE CLUSTER IS INCLUDED
      if (includeClusters != null) {
        if (!includeClusters.contains(clusterName))
          continue;
      } else if (excludeClusters != null) {
        if (excludeClusters.contains(clusterName))
          continue;
      }

      clusterId = storage1.getClusterIdByName(clusterName);

      OClusterPosition[] db1Range = storage1.getClusterDataRange(clusterId);
      OClusterPosition[] db2Range = storage2.getClusterDataRange(clusterId);

      final OClusterPosition db1Max = db1Range[1];
      final OClusterPosition db2Max = db2Range[1];

      ODatabaseRecordThreadLocal.INSTANCE.set(databaseDocumentTxOne);
      final ODocument doc1 = new ODocument();
      ODatabaseRecordThreadLocal.INSTANCE.set(databaseDocumentTxTwo);
      final ODocument doc2 = new ODocument();

      final ORecordId rid = new ORecordId(clusterId);

      // TODO why this maximums can be different?
      final OClusterPosition clusterMax = db1Max.compareTo(db2Max) > 0 ? db1Max : db2Max;

      final OStorage storage;

      if (clusterMax.equals(db1Max))
        storage = storage1;
      else
        storage = storage2;

      OPhysicalPosition[] physicalPositions = storage.ceilingPhysicalPositions(clusterId, new OPhysicalPosition(
          OClusterPositionFactory.INSTANCE.valueOf(0)));

      long recordsCounter = 0;
      while (physicalPositions.length > 0) {
        for (OPhysicalPosition physicalPosition : physicalPositions) {
          recordsCounter++;

          final OClusterPosition position = physicalPosition.clusterPosition;
          rid.clusterPosition = position;

          if (isDocumentDatabases() && rid.equals(new ORecordId(storage1.getConfiguration().indexMgrRecordId))
              && rid.equals(new ORecordId(storage2.getConfiguration().indexMgrRecordId)))
            continue;
          if (isDocumentDatabases() && rid.equals(new ORecordId(storage1.getConfiguration().schemaRecordId))
              && rid.equals(new ORecordId(storage2.getConfiguration().schemaRecordId)))
            continue;

          final ORawBuffer buffer1 = storage1.readRecord(rid, null, true, null, false, OStorage.LOCKING_STRATEGY.DEFAULT)
              .getResult();
          final ORawBuffer buffer2;
          if (ridMapper == null)
            buffer2 = storage2.readRecord(rid, null, true, null, false, OStorage.LOCKING_STRATEGY.DEFAULT).getResult();
          else {
            final ORID newRid = ridMapper.map(rid);
            if (newRid == null)
              buffer2 = storage2.readRecord(rid, null, true, null, false, OStorage.LOCKING_STRATEGY.DEFAULT).getResult();
            else
              buffer2 = storage2.readRecord(new ORecordId(newRid), null, true, null, false, OStorage.LOCKING_STRATEGY.DEFAULT)
                  .getResult();
          }

          if (buffer1 == null && buffer2 == null)
            // BOTH RECORD NULL, OK
            continue;
          else if (buffer1 == null && buffer2 != null) {
            // REC1 NULL
            listener.onMessage("\n- ERR: RID=" + clusterId + ":" + position + " is null in DB1");
            ++differences;
          } else if (buffer1 != null && buffer2 == null) {
            // REC2 NULL
            listener.onMessage("\n- ERR: RID=" + clusterId + ":" + position + " is null in DB2");
            ++differences;
          } else {
            if (buffer1.recordType != buffer2.recordType) {
              listener.onMessage("\n- ERR: RID=" + clusterId + ":" + position + " recordType is different: "
                  + (char) buffer1.recordType + " <-> " + (char) buffer2.recordType);
              ++differences;
            }

            if (buffer1.buffer == null && buffer2.buffer == null) {
            } else if (buffer1.buffer == null && buffer2.buffer != null) {
              listener.onMessage("\n- ERR: RID=" + clusterId + ":" + position + " content is different: null <-> "
                  + buffer2.buffer.length);
              ++differences;

            } else if (buffer1.buffer != null && buffer2.buffer == null) {
              listener.onMessage("\n- ERR: RID=" + clusterId + ":" + position + " content is different: " + buffer1.buffer.length
                  + " <-> null");
              ++differences;

            } else {
              if (buffer1.recordType == ODocument.RECORD_TYPE) {
                // DOCUMENT: TRY TO INSTANTIATE AND COMPARE

                makeDbCall(databaseDocumentTxOne, new ODocumentHelper.ODbRelatedCall<Object>() {
                  public Object call() {
                    doc1.reset();
                    doc1.fromStream(buffer1.buffer);
                    return null;
                  }
                });

                makeDbCall(databaseDocumentTxTwo, new ODocumentHelper.ODbRelatedCall<Object>() {
                  public Object call() {
                    doc2.reset();
                    doc2.fromStream(buffer2.buffer);
                    return null;
                  }
                });

                if (rid.toString().equals(storage1.getConfiguration().schemaRecordId)
                    && rid.toString().equals(storage2.getConfiguration().schemaRecordId)) {
                  makeDbCall(databaseDocumentTxOne, new ODocumentHelper.ODbRelatedCall<java.lang.Object>() {
                    public Object call() {
                      convertSchemaDoc(doc1);
                      return null;
                    }
                  });

                  makeDbCall(databaseDocumentTxTwo, new ODocumentHelper.ODbRelatedCall<java.lang.Object>() {
                    public Object call() {
                      convertSchemaDoc(doc2);
                      return null;
                    }
                  });
                }

                if (!ODocumentHelper.hasSameContentOf(doc1, databaseDocumentTxOne, doc2, databaseDocumentTxTwo, ridMapper)) {
                  listener.onMessage("\n- ERR: RID=" + clusterId + ":" + position + " document content is different");
                  listener.onMessage("\n--- REC1: " + new String(buffer1.buffer));
                  listener.onMessage("\n--- REC2: " + new String(buffer2.buffer));
                  listener.onMessage("\n");
                  ++differences;
                }
              } else {
                if (buffer1.buffer.length != buffer2.buffer.length) {
                  // CHECK IF THE TRIMMED SIZE IS THE SAME
                  final String rec1 = new String(buffer1.buffer).trim();
                  final String rec2 = new String(buffer2.buffer).trim();

                  if (rec1.length() != rec2.length()) {
                    listener.onMessage("\n- ERR: RID=" + clusterId + ":" + position + " content length is different: "
                        + buffer1.buffer.length + " <-> " + buffer2.buffer.length);

                    if (buffer1.recordType == ODocument.RECORD_TYPE || buffer1.recordType == ORecordFlat.RECORD_TYPE)
                      listener.onMessage("\n--- REC1: " + rec1);
                    if (buffer2.recordType == ODocument.RECORD_TYPE || buffer2.recordType == ORecordFlat.RECORD_TYPE)
                      listener.onMessage("\n--- REC2: " + rec2);
                    listener.onMessage("\n");

                    ++differences;
                  }
                } else {
                  // CHECK BYTE PER BYTE
                  for (int b = 0; b < buffer1.buffer.length; ++b) {
                    if (buffer1.buffer[b] != buffer2.buffer[b]) {
                      listener.onMessage("\n- ERR: RID=" + clusterId + ":" + position + " content is different at byte #" + b
                          + ": " + buffer1.buffer[b] + " <-> " + buffer2.buffer[b]);
                      listener.onMessage("\n--- REC1: " + new String(buffer1.buffer));
                      listener.onMessage("\n--- REC2: " + new String(buffer2.buffer));
                      listener.onMessage("\n");
                      ++differences;
                      break;
                    }
                  }
                }
              }
            }
          }
        }

        physicalPositions = storage.higherPhysicalPositions(clusterId, physicalPositions[physicalPositions.length - 1]);
        if (recordsCounter % 10000 == 0)
          listener.onMessage("\n" + recordsCounter + " records were processed for cluster " + clusterName + " ...");
      }

      listener.onMessage("\nCluster comparison was finished, " + recordsCounter + " records were processed for cluster "
          + clusterName + " ...");
    }

    return true;
  }

  private void convertSchemaDoc(final ODocument document) {
    if (document.field("classes") != null) {
      document.setFieldType("classes", OType.EMBEDDEDSET);
      for (ODocument classDoc : document.<Set<ODocument>> field("classes")) {
        classDoc.setFieldType("properties", OType.EMBEDDEDSET);
      }
    }
  }

  private boolean isDocumentDatabases() {
    return storage1.getConfiguration().schemaRecordId != null && storage2.getConfiguration().schemaRecordId != null;
  }

  private void reportIndexDiff(OIndex<?> indexOne, Object key, final Object indexOneValue, final Object indexTwoValue) {
    listener.onMessage("\n- ERR: Entry values for key '" + key + "' are different for index " + indexOne.getName());
    listener.onMessage("\n--- DB1: " + makeDbCall(databaseDocumentTxOne, new ODbRelatedCall<String>() {
      public String call() {
        return indexOneValue.toString();
      }
    }));
    listener.onMessage("\n--- DB2: " + makeDbCall(databaseDocumentTxOne, new ODbRelatedCall<String>() {
      public String call() {
        return indexTwoValue.toString();
      }
    }));
    listener.onMessage("\n");
    ++differences;
  }
}
TOP

Related Classes of com.orientechnologies.orient.core.db.tool.ODatabaseCompare

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.