Create the update query from object diff

@param   object  $document   The document that the update query will match
@return  array
768607
public function getUpdateQuery($document)
    {
        $updateQuery = [];
        $old = $this->uncacheObject($document);
        $new = $this->hydrator->unhydrate($document);
        unset($new["stream"]);

        return $this->updateQueryCreator->createUpdateQuery($old, $new);
    }
568883
public function insert(&$document) {

        if (isset($document[0])) {

            $this->database->connection->beginTransaction();

            foreach ($document as &$doc) {

                if (!\is_array($doc)) continue;

                $res = $this->_insert($doc);

                if (!$res) {
                    $this->database->connection->rollBack();
                    return $res;
                }
            }
            $this->database->connection->commit();
            return \count($document);
        } else {
            return $this->_insert($document);
        }
    }
1222802
private function getQueryForDocument(object $document) : array
    {
        $id = $this->uow->getDocumentIdentifier($document);
        $id = $this->class->getDatabaseIdentifierValue($id);

        $shardKeyQueryPart = $this->getShardKeyQuery($document);
        return array_merge(['_id' => $id], $shardKeyQueryPart);
    }
113830
public function computeChangeSet(ClassMetadata $class, $document)
    {
        if ($document instanceof Proxy && !$document->__isInitialized()) {
            return;
        }

        $oid = spl_object_hash($document);
        if (in_array($oid, $this->changesetComputed)) {
            return;
        }

        $this->changesetComputed[] = $oid;

        $changeSet = $actualData = $this->getDocumentActualData($class, $document);
        $id = $this->getDocumentId($document, false);
        $isNew = !isset($this->originalData[$oid]);

        if ($isNew) {
            // Document is New and should be inserted
            $this->originalData[$oid] = $changeSet;
        } elseif (!empty($this->documentChangesets[$oid]['fields'])) {
            foreach ($this->documentChangesets[$oid]['fields'] as $fieldName => $data) {
                $this->originalData[$oid][$fieldName] = $data[0];
            }
        }

        if ($class->parentMapping && isset($changeSet[$class->parentMapping])) {
            $parent = $changeSet[$class->parentMapping];
            $parentClass = $this->dm->getClassMetadata(get_class($parent));
            $state = $this->getDocumentState($parent);

            if ($state === self::STATE_MANAGED) {
                $this->computeChangeSet($parentClass, $parent);
            }
        }

        foreach ($class->childMappings as $fieldName) {
            if ($changeSet[$fieldName]) {
                if (is_array($changeSet[$fieldName]) || $changeSet[$fieldName] instanceof Collection) {
                    throw PHPCRException::childFieldIsArray(
                        self::objToStr($document, $this->dm),
                        $fieldName
                    );
                }

                if (!is_object($changeSet[$fieldName])) {
                    throw PHPCRException::childFieldNoObject(
                        self::objToStr($document, $this->dm),
                        $fieldName,
                        gettype($changeSet[$fieldName])
                    );
                }

                $mapping = $class->mappings[$fieldName];
                $changeSet[$fieldName] = $this->computeChildChanges($mapping, $changeSet[$fieldName], $id, $mapping['nodeName']);
            }
        }

        $this->computeAssociationChanges($document, $class, $oid, $isNew, $changeSet, 'reference');
        $this->computeAssociationChanges($document, $class, $oid, $isNew, $changeSet, 'referrer');

        foreach ($class->mixedReferrersMappings as $fieldName) {
            if ($changeSet[$fieldName]
                && $changeSet[$fieldName] instanceof PersistentCollection
                && $changeSet[$fieldName]->isDirty()
            ) {
                throw new PHPCRException("The immutable mixed referrer collection in field $fieldName is dirty");
            }
        }

        $this->computeChildrenChanges($document, $class, $oid, $isNew, $changeSet);

        if (!$isNew) {
            // collect assignment move operations
            $destPath = $destName = false;

            if (isset($this->originalData[$oid][$class->parentMapping], $changeSet[$class->parentMapping])
                && $this->originalData[$oid][$class->parentMapping] !== $changeSet[$class->parentMapping]
            ) {
                $destPath = $this->getDocumentId($changeSet[$class->parentMapping]);
            }

            if (isset($this->originalData[$oid][$class->nodename], $changeSet[$class->nodename])
                && $this->originalData[$oid][$class->nodename] !== $changeSet[$class->nodename]
            ) {
                $destName = $changeSet[$class->nodename];
            }

            // there was assignment move
            if ($destPath || $destName) {
                // add the other field if only one was changed
                if (false === $destPath) {
                    $destPath = isset($changeSet[$class->parentMapping])
                        ? $this->getDocumentId($changeSet[$class->parentMapping])
                        : PathHelper::getParentPath($this->getDocumentId($document));
                }
                if (false === $destName) {
                    $destName = $class->nodename !== null && $changeSet[$class->nodename]
                        ? $changeSet[$class->nodename]
                        : PathHelper::getNodeName($this->getDocumentId($document));
                }

                // make sure destination nodename is okay
                if ($exception = $class->isValidNodename($destName)) {
                    throw IdException::illegalName($document, $class->nodename, $destName);
                }

                // prevent path from becoming "//foobar" when moving to root node.
                $targetPath = ('/' == $destPath) ? "/$destName" : "$destPath/$destName";

                $this->scheduleMove($document, $targetPath);
            }

            if (isset($this->originalData[$oid][$class->identifier], $changeSet[$class->identifier])
                && $this->originalData[$oid][$class->identifier] !== $changeSet[$class->identifier]
            ) {
                throw new PHPCRException('The Id is immutable ('.$this->originalData[$oid][$class->identifier].' !== '.$changeSet[$class->identifier].'). Please use DocumentManager::move to move the document: '.self::objToStr($document, $this->dm));
            }
        }

        $fields = array_intersect_key($changeSet, $class->mappings);

        if ($this->isDocumentTranslatable($class)) {
            $locale = $this->getCurrentLocale($document, $class);

            // ensure we do not bind a previously removed translation
            if (!$this->isTranslationRemoved($document, $locale)) {
                $this->doBindTranslation($document, $locale, $class);
            }
        }

        if ($isNew) {
            $this->documentChangesets[$oid]['fields'] = $fields;
            $this->scheduledInserts[$oid] = $document;

            return;
        }

        $translationChanges = false;
        if ($this->isDocumentTranslatable($class)) {
            $oid = spl_object_hash($document);
            if (isset($this->documentTranslations[$oid])) {
                foreach ($this->documentTranslations[$oid] as $localeToCheck => $data) {
                    // a translation was removed
                    if (empty($data)) {
                        $translationChanges = true;
                        break;
                    }
                    // a translation was added
                    if (empty($this->originalTranslatedData[$oid][$localeToCheck])) {
                        $translationChanges = true;
                        break;
                    }
                    // a translation was changed
                    foreach ($data as $fieldName => $fieldValue) {
                        if ($this->originalTranslatedData[$oid][$localeToCheck][$fieldName] !== $fieldValue) {
                            $translationChanges = true;
                            break;
                        }
                    }
                }
            }

            // ensure that locale changes are not considered a change in the document
            if ($class->localeMapping && array_key_exists($class->localeMapping, $fields)) {
                unset($fields[$class->localeMapping]);
            }
        }

        foreach ($fields as $fieldName => $fieldValue) {
            $keepChange = false;
            if ($fieldValue instanceof ReferenceManyCollection || $fieldValue instanceof ReferrersCollection) {
                if ($fieldValue->changed()) {
                    $keepChange = true;
                }
            } elseif ($this->originalData[$oid][$fieldName] !== $fieldValue) {
                $keepChange = true;
            }

            if ($keepChange) {
                $fields[$fieldName] = [$this->originalData[$oid][$fieldName], $fieldValue];
            } else {
                unset($fields[$fieldName]);
            }
        }

        if (!empty($fields) || $translationChanges) {
            $this->documentChangesets[$oid]['fields'] = $fields;
            $this->originalData[$oid] = $actualData;
            $this->scheduledUpdates[$oid] = $document;
        } elseif (empty($this->documentChangesets[$oid]['reorderings'])) {
            unset($this->documentChangesets[$oid], $this->scheduledUpdates[$oid]);
        } else {
            $this->documentChangesets[$oid]['fields'] = [];
        }
    }
902717
static function getDocumentMap($document){
        // TODO: hadi 1396-07: It is better to move this function to Collection_Document class
        $attr = new Collection_Attribute();
        $map = $attr->getList(
            array(
                'filter' => 'document=' . $document->id
            ));
        $result = array();
        $iterator = $map->getIterator();
        while ($iterator->valid()) {
            $attr = $iterator->current();
            $result[$attr->key] = $attr->value;
            $iterator->next();
        }
        
        $result['id'] = $document->id;
        $result['collection'] = $document->collection;
        return $result;
    }
760458
public function getDocumentActualData($document)
    {
        $class      = $this->dm->getClassMetadata(get_class($document));
        $actualData = [];

        foreach ($class->propertyMetadata as $name => $mapping) {
            // skip not saved attributes
            if ($mapping->notSaved) {
                continue;
            }

            $value = $mapping->getValue($document);

            if ($mapping->isMany() && $value !== null && !($value instanceof PersistentCollectionInterface)) {
                // If $actualData[$name] is not a Collection then use an ArrayCollection.
                if (!$value instanceof Collection) {
                    $value = new ArrayCollection($value);
                }

                // Inject PersistentCollection
                $coll = $this->dm->getConfiguration()->getPersistentCollectionFactory()->create($this->dm, $mapping, $value);
                $coll->setOwner($document, $mapping);
                $coll->setDirty(!$value->isEmpty());
                $mapping->setValue($document, $coll);
                $actualData[$name] = $coll;
            } else {
                $actualData[$name] = $value;
            }
        }

        return $actualData;
    }
1066700
public static function bulkUpdate($documents)
    {
        $instance = new static();

        $primaryKey = $instance->getPrimaryKey();
        $allowNull = $instance->isAllowNullValue();
        $fieldTypes = $instance->getFieldTypes();
        foreach ($documents as $i => $document) {
            if (!isset($document[$primaryKey])) {
                throw new InvalidValueException(['bulkUpdate `:model` model must set primary value', 'model' => static::class]);
            }
            foreach ((array)$document as $field => $value) {
                if ($value === null) {
                    $document[$field] = $allowNull ? null : $instance->normalizeValue($fieldTypes[$field], '');
                } else {
                    $document[$field] = $instance->normalizeValue($fieldTypes[$field], $value);
                }
            }
        }

        /**
         * @var \ManaPHP\MongodbInterface $connection
         */
        $connection = $instance->getConnection();
        return $connection->bulkUpdate($instance->getSource(), $documents, $primaryKey);
    }
212353
public function update($collectionName, $condition, $document, $options = [])
    {
        $batchOptions = [];
        foreach (['bypassDocumentValidation'] as $name) {
            if (isset($options[$name])) {
                $batchOptions[$name] = $options[$name];
                unset($options[$name]);
            }
        }

        $this->document = [];
        $this->addUpdate($condition, $document, $options);
        $result = $this->executeBatch($collectionName, $batchOptions);

        return $result['result'];
    }
677457
private function buildAggregateAsArray($document)
    {
        $returnArray = [];

        foreach ($document as $event) {
            $returnArray[] = [
                'uuid' => (string) $event->uuid,
                'version' => $event->version,
                'payload' => $event->payload,
                'type' => $event->type,
                'body' => unserialize($event->body),
                'occurred_on' => $event->occurred_on
            ];
        }

        return $returnArray;
    }
1210253
public function getObjectPropertyAnnotations($document)
    {
        $cacheKey = "object_property_annotations_" . get_class($document);
        if (!$this->cache->contains($cacheKey)) {
            $refObject   = new \ReflectionObject($document);
            $annotations = array();
            foreach ($refObject->getProperties() as $property) {
                $annotation = $this->getPropertyAnnotation($property);
                if ($annotation) {
                    $annotations[$property->getName()] = $annotation;
                }
            }
            $this->cache->save($cacheKey, $annotations);
        }
        return $this->cache->fetch($cacheKey);
    }
127030
public function deleteDocument(Document $document)
    {
        $options = $document->getOptions(
            [
                'version',
                'version_type',
                'routing',
                'parent',
                'replication',
                'consistency',
                'refresh',
                'timeout',
            ]
        );

        return $this->deleteById($document->getId(), $options);
    }
341309
private static function filterDocument($document)
    {
        if ($document instanceof Stream || $document instanceof SplFileObject) {
            return $document;
        }

        throw new TypeError(sprintf(
            '%s::parse expects parameter 1 to be a %s or a SplFileObject object, %s given',
            self::class,
            Stream::class,
            is_object($document) ? get_class($document) : gettype($document)
        ));
    }
11383
public function scheduleForUpdate($document)
    {
        $oid = spl_object_hash($document);
        if ( ! isset($this->documentIdentifiers[$oid])) {
            throw new \InvalidArgumentException("Document has no identity.");
        }
        if (isset($this->documentDeletions[$oid])) {
            throw new \InvalidArgumentException("Document is removed.");
        }

        if ( ! isset($this->documentUpdates[$oid]) && ! isset($this->documentInsertions[$oid]) && ! isset($this->documentUpserts[$oid])) {
            $this->documentUpdates[$oid] = $document;
        }
    }
714049
private function filterDiffs($compRes)
    {
        $result = [];

        if (is_array($this->config['updateTypes'])) {
            $updateActions = $this->config['updateTypes'];
        } else {
            $updateActions = array_map('trim', explode(',', $this->config['updateTypes']));
        }

        $allowedActions = ['create', 'drop', 'add', 'remove', 'modify'];
        $updateActions = array_intersect($updateActions, $allowedActions);

        foreach ($compRes as $table => $info) {
            if ($info['sourceOrphan']) {
                if (in_array('create', $updateActions)) {
                    $result[$table] = $info;
                }
            } elseif ($info['destOrphan']) {
                if (in_array('drop', $updateActions)) {
                    $result[$table] = $info;
                }
            } elseif ($info['differs']) {
                $resultInfo = $info;
                unset($resultInfo['differs']);

                foreach ($info['differs'] as $diff) {
                    if (empty($diff['dest']) && in_array('add', $updateActions)) {
                        $resultInfo['differs'][] = $diff;
                    } elseif (empty($diff['source']) && in_array('remove', $updateActions)) {
                        $resultInfo['differs'][] = $diff;
                    } elseif (in_array('modify', $updateActions)) {
                        $resultInfo['differs'][] = $diff;
                    }
                }

                if (!empty($resultInfo['differs'])) {
                    $result[$table] = $resultInfo;
                }
            }
        }
        return $result;
    }
476576
public function prepareInsertData($document)
    {
        $class = $this->dm->getClassMetadata(get_class($document));
        $changeset = $this->uow->getDocumentChangeSet($document);

        $insertData = array();
        foreach ($class->fieldMappings as $mapping) {

            // @ReferenceMany and @EmbedMany are inserted later
            if ($mapping['type'] === ClassMetadata::MANY) {
                continue;
            }

            $new = isset($changeset[$mapping['fieldName']][1]) ? $changeset[$mapping['fieldName']][1] : null;

            // Don't store null values unless nullable === true
            if ($new === null && $mapping['nullable'] === false) {
                continue;
            }

            $value = null;
            if ($new !== null) {
                // @Field, @String, @Date, etc.
                if ( ! isset($mapping['association'])) {
                    $value = Type::getType($mapping['type'])->convertToDatabaseValue($new);

                // @ReferenceOne
                } elseif (isset($mapping['association']) && $mapping['association'] === ClassMetadata::REFERENCE_ONE) {
                    if ($mapping['isInverseSide']) {
                        continue;
                    }

                    $value = $this->prepareReferencedDocumentValue($mapping, $new);

                // @EmbedOne
                } elseif (isset($mapping['association']) && $mapping['association'] === ClassMetadata::EMBED_ONE) {
                    $value = $this->prepareEmbeddedDocumentValue($mapping, $new);
                }
            }

            $insertData[$mapping['name']] = $value;
        }

        // add discriminator if the class has one
        if (isset($class->discriminatorField)) {
            $insertData[$class->discriminatorField] = isset($class->discriminatorValue)
                ? $class->discriminatorValue
                : $class->name;
        }

        return $insertData;
    }
249857
private function _updateObjectIndexes($object)
    {
        //delete all indexed references to this object
        $update = Statement::get(
            'DELETE_OBJECT_INDEX',
            [
                '@collection' => $this->_name,
                '@id' => $this->_connector->quote($object->__id)
            ]
        );
        //parse each property of the object an attempt to index each value
        foreach (get_object_vars($object) as $property => $value) {
            if (
                $this->_isPropertyIndexable($property)
                && $this->_isValueIndexable($value)
            ) {
                $insert = Statement::get(
                    'INSERT_OBJECT_INDEX',
                    [
                        '@collection' => $this->_name,
                        '@type' => $this->_connector->quote('value'),
                        '@prop' => $this->_connector->quote($property),
                        '@val' => $this->_connector->quote($value),
                        '@id' => $this->_connector->quote($object->__id),
                        '@origin' => $this->_connector->quote($object->__origin)
                    ]
                );
                $update .= $insert;
            }
        }
        //add the object registry index
        $insert = Statement::get(
            'INSERT_OBJECT_INDEX',
            [
                '@collection' => $this->_name,
                '@type' => $this->_connector->quote('registry'),
                '@prop' => $this->_connector->quote(''),
                '@val' => $this->_connector->quote(''),
                '@id' => $this->_connector->quote($object->__id),
                '@origin' => $this->_connector->quote($object->__origin)
            ]
        );
        $update .= $insert;
        //execute all the sql to update indexes.
        $this->_connector->exec($update);
    }
1098548
public function getFieldValue($document, $field)
    {
        if (isset($this->reflFields[$field])) {
            return $this->reflFields[$field]->getValue($document);
        }
    }