diff --git a/src/Database/Database.php b/src/Database/Database.php index c1fdc26f2..5d6ab1ff1 100644 --- a/src/Database/Database.php +++ b/src/Database/Database.php @@ -2919,25 +2919,83 @@ public function updateDocument(string $collection, string $id, Document $documen if ($collection->getId() !== self::METADATA) { $documentSecurity = $collection->getAttribute('documentSecurity', false); - $relationshipKeys = []; foreach ($relationships as $relationship) { - $relationshipKey = $relationship->getAttribute('key'); - $relationshipKeys[$relationshipKey] = $relationshipKey; + $relationships[$relationship->getAttribute('key')] = $relationship; } + // Compare if the document has any changes - foreach ($document as $key=>$value) { + foreach ($document as $key => $value) { // Skip the nested documents as they will be checked later in recursions. - if (array_key_exists($key, $relationshipKeys)) { + if (\array_key_exists($key, $relationships)) { + $relationType = (string) $relationships[$key]['options']['relationType']; + $side = (string) $relationships[$key]['options']['side']; + + switch($relationType) { + case Database::RELATION_ONE_TO_ONE: + $oldValue = $old->getAttribute($key) instanceof Document + ? $old->getAttribute($key)->getId() + : $old->getAttribute($key); + + if ((\is_null($value) !== \is_null($oldValue)) + || (\is_string($value) && $value !== $oldValue) + || ($value instanceof Document && $value->getId() !== $oldValue)) { + $shouldUpdate = true; + } + break; + case Database::RELATION_ONE_TO_MANY: + case Database::RELATION_MANY_TO_ONE: + case Database::RELATION_MANY_TO_MANY: + if ( + ($relationType === Database::RELATION_MANY_TO_ONE && $side === Database::RELATION_SIDE_PARENT) || + ($relationType === Database::RELATION_ONE_TO_MANY && $side === Database::RELATION_SIDE_CHILD) + ) { + $oldValue = $old->getAttribute($key) instanceof Document + ? $old->getAttribute($key)->getId() + : $old->getAttribute($key); + + if ((\is_null($value) !== \is_null($oldValue)) + || (\is_string($value) && $value !== $oldValue) + || ($value instanceof Document && $value->getId() !== $oldValue)) { + $shouldUpdate = true; + } + break; + } + + if ((\is_null($old->getAttribute($key)) !== \is_null($value)) + || \count($old->getAttribute($key)) !== \count($value)) { + $shouldUpdate = true; + break; + } + foreach ($value as $index => $relation) { + $oldValue = $old->getAttribute($key)[$index] instanceof Document + ? $old->getAttribute($key)[$index]->getId() + : $old->getAttribute($key)[$index]; + + if ((\is_string($relation) && $relation !== $oldValue) + || ($relation instanceof Document && $relation->getId() !== $oldValue)) { + $shouldUpdate = true; + break; + } + } + break; + } + + if ($shouldUpdate) { + break; + } + continue; } - $oldAttributeValue = $old->getAttribute($key); + $oldValue = $old->getAttribute($key); + // If values are not equal we need to update document. - if ($oldAttributeValue !== $value) { + if ($value !== $oldValue) { $shouldUpdate = true; break; } } + if ($shouldUpdate && !$validator->isValid([ ...$collection->getUpdate(), ...($documentSecurity ? $old->getUpdate() : []) @@ -2948,8 +3006,6 @@ public function updateDocument(string $collection, string $id, Document $documen if ($shouldUpdate) { $document->setAttribute('$updatedAt', $time); - } else { - $document->setAttribute('$updatedAt', $old->getUpdatedAt()); } // Check if document was updated after the request timestamp @@ -2979,6 +3035,7 @@ public function updateDocument(string $collection, string $id, Document $documen $document = $this->decode($collection, $document); $this->purgeRelatedDocuments($collection, $id); + $this->cache->purge('cache-' . $this->getNamespace() . ':' . $collection->getId() . ':' . $id . ':*'); $this->trigger(self::EVENT_DOCUMENT_UPDATE, $document); diff --git a/tests/Database/Base.php b/tests/Database/Base.php index 1d77935c9..b2481a388 100644 --- a/tests/Database/Base.php +++ b/tests/Database/Base.php @@ -3221,15 +3221,14 @@ public function testWritePermissionsUpdateFailure(Document $document): Document return $document; } - /** * @depends testCreateDocument */ public function testNoChangeUpdateDocumentWithoutPermission(Document $document): Document { - Authorization::setRole(Role::any()->toString()); - $document = static::getDatabase()->createDocument('documents', new Document([ + '$id' => ID::unique(), + '$permissions' => [], 'string' => 'text📝', 'integer' => 5, 'bigint' => 8589934592, // 2^33 @@ -3238,10 +3237,14 @@ public function testNoChangeUpdateDocumentWithoutPermission(Document $document): 'colors' => ['pink', 'green', 'blue'], ])); - Authorization::cleanRoles(); - $updatedDocument = static::getDatabase()->updateDocument('documents', $document->getId(), $document); + $updatedDocument = static::getDatabase()->updateDocument( + 'documents', + $document->getId(), + $document + ); - // Document should not be updated as there is no change. It should also not throw any authorization exception without any permission because of no change. + // Document should not be updated as there is no change. + // It should also not throw any authorization exception without any permission because of no change. $this->assertEquals($updatedDocument->getUpdatedAt(), $document->getUpdatedAt()); return $document; @@ -3254,17 +3257,9 @@ public function testNoChangeUpdateDocumentWithRelationWithoutPermission(): void return; } - Authorization::cleanRoles(); - Authorization::setRole(Role::user('a')->toString()); + static::getDatabase()->createCollection('parentRelationTest'); + static::getDatabase()->createCollection('childRelationTest'); - static::getDatabase()->createCollection('parentRelationTest', [], [], [ - Permission::read(Role::user('a')), - Permission::create(Role::user('a')), - ]); - static::getDatabase()->createCollection('childRelationTest', [], [], [ - Permission::read(Role::user('a')), - Permission::create(Role::user('a')), - ]); static::getDatabase()->createAttribute('parentRelationTest', 'name', Database::VAR_STRING, 255, false); static::getDatabase()->createAttribute('childRelationTest', 'name', Database::VAR_STRING, 255, false); @@ -3272,35 +3267,35 @@ public function testNoChangeUpdateDocumentWithRelationWithoutPermission(): void collection: 'parentRelationTest', relatedCollection: 'childRelationTest', type: Database::RELATION_ONE_TO_MANY, - id: 'childs' + id: 'children' ); // Create document with relationship with nested data $parent = static::getDatabase()->createDocument('parentRelationTest', new Document([ '$id' => 'parent1', + '$permissions' => [ + Permission::read(Role::any()), + ], 'name' => 'Parent 1', - 'childs' => [ + 'children' => [ [ '$id' => 'child1', + '$permissions' => [ + Permission::read(Role::any()), + ], 'name' => 'Child 1', ], ], ])); - $this->assertEquals(1, \count($parent['childs'])); - $updatedParent = static::getDatabase()->updateDocument('parentRelationTest', 'parent1', new Document([ - '$id' => 'parent1', - 'name'=>'Parent 1', - '$collection' => 'parentRelationTest', - 'childs' => [ - new Document([ - '$id' => 'child1', - '$collection' => 'childRelationTest' - ]), - ] - ])); + + $this->assertEquals(1, \count($parent['children'])); + + $parent->setAttribute('children', ['child1']); + + $updatedParent = static::getDatabase()->updateDocument('parentRelationTest', 'parent1', $parent); $this->assertEquals($updatedParent->getUpdatedAt(), $parent->getUpdatedAt()); - $this->assertEquals($updatedParent->getAttribute('childs')[0]->getUpdatedAt(), $parent->getAttribute('childs')[0]->getUpdatedAt()); + $this->assertEquals($updatedParent->getAttribute('children')[0]->getUpdatedAt(), $parent->getAttribute('children')[0]->getUpdatedAt()); static::getDatabase()->deleteCollection('parentRelationTest'); static::getDatabase()->deleteCollection('childRelationTest'); @@ -3309,7 +3304,7 @@ public function testNoChangeUpdateDocumentWithRelationWithoutPermission(): void public function testExceptionAttributeLimit(): void { if ($this->getDatabase()->getLimitForAttributes() > 0) { - // load the collection up to the limit + // Load the collection up to the limit $attributes = []; for ($i = 0; $i < $this->getDatabase()->getLimitForAttributes(); $i++) { $attributes[] = new Document([ @@ -3323,7 +3318,8 @@ public function testExceptionAttributeLimit(): void 'filters' => [], ]); } - $collection = static::getDatabase()->createCollection('attributeLimit', $attributes); + + static::getDatabase()->createCollection('attributeLimit', $attributes); $this->expectException(LimitException::class); $this->assertEquals(false, static::getDatabase()->createAttribute('attributeLimit', "breaking", Database::VAR_INTEGER, 0, true)); @@ -11588,7 +11584,6 @@ public function testCollectionPermissionsRelationshipsUpdateWorks(array $data): Authorization::cleanRoles(); Authorization::setRole(Role::users()->toString()); - static::getDatabase()->updateDocument( $collection->getId(), $document->getId(),