diff --git a/src/Database/Database.php b/src/Database/Database.php index 0350a658b..c30294d7f 100644 --- a/src/Database/Database.php +++ b/src/Database/Database.php @@ -1462,17 +1462,8 @@ public function createAttribute(string $collection, string $id, string $type, in throw new DatabaseException("Attribute of type: $type requires the following filters: " . implode(",", $requiredFilters)); } - if ( - $this->adapter->getLimitForAttributes() > 0 && - $this->adapter->getCountOfAttributes($collection) >= $this->adapter->getLimitForAttributes() - ) { - throw new LimitException('Column limit reached. Cannot create new attribute.'); - } - - if ($format) { - if (!Structure::hasFormat($format, $type)) { - throw new DatabaseException('Format ("' . $format . '") not available for this attribute type ("' . $type . '")'); - } + if ($format && !Structure::hasFormat($format, $type)) { + throw new DatabaseException('Format ("' . $format . '") not available for this attribute type ("' . $type . '")'); } $attribute = new Document([ @@ -1489,14 +1480,13 @@ public function createAttribute(string $collection, string $id, string $type, in 'filters' => $filters, ]); - $collection->setAttribute('attributes', $attribute, Document::SET_TYPE_APPEND); + $this->checkAttribute($collection, $attribute); - if ( - $this->adapter->getDocumentSizeLimit() > 0 && - $this->adapter->getAttributeWidth($collection) >= $this->adapter->getDocumentSizeLimit() - ) { - throw new LimitException('Row width limit reached. Cannot create new attribute.'); - } + $collection->setAttribute( + 'attributes', + $attribute, + Document::SET_TYPE_APPEND + ); switch ($type) { case self::VAR_STRING: @@ -1504,7 +1494,6 @@ public function createAttribute(string $collection, string $id, string $type, in throw new DatabaseException('Max size allowed for string is: ' . number_format($this->adapter->getLimitForString())); } break; - case self::VAR_INTEGER: $limit = ($signed) ? $this->adapter->getLimitForInt() / 2 : $this->adapter->getLimitForInt(); if ($size > $limit) { @@ -2259,8 +2248,24 @@ public function createRelationship( } $this->silent(function () use ($collection, $relatedCollection, $type, $twoWay, $id, $twoWayKey) { - $this->updateDocument(self::METADATA, $collection->getId(), $collection); - $this->updateDocument(self::METADATA, $relatedCollection->getId(), $relatedCollection); + try { + $this->withTransaction(function () use ($collection, $relatedCollection) { + $this->updateDocument(self::METADATA, $collection->getId(), $collection); + $this->updateDocument(self::METADATA, $relatedCollection->getId(), $relatedCollection); + }); + } catch (\Throwable $e) { + $this->adapter->deleteRelationship( + $collection->getId(), + $relatedCollection->getId(), + $type, + $twoWay, + $id, + $twoWayKey, + Database::RELATION_SIDE_PARENT + ); + + throw new DatabaseException('Failed to create relationship: ' . $e->getMessage()); + } $indexKey = '_index_' . $id; $twoWayIndexKey = '_index_' . $twoWayKey; @@ -2328,13 +2333,13 @@ public function updateRelationship( !\is_null($newKey) && \in_array($newKey, \array_map(fn ($attribute) => $attribute['key'], $attributes)) ) { - throw new DuplicateException('Attribute already exists'); + throw new DuplicateException('Relationship already exists'); } $attributeIndex = array_search($id, array_map(fn ($attribute) => $attribute['$id'], $attributes)); if ($attributeIndex === false) { - throw new NotFoundException('Attribute not found'); + throw new NotFoundException('Relationship not found'); } $attribute = $attributes[$attributeIndex]; @@ -2521,7 +2526,7 @@ public function deleteRelationship(string $collection, string $id): bool } if (\is_null($relationship)) { - throw new NotFoundException('Attribute not found'); + throw new NotFoundException('Relationship not found'); } $collection->setAttribute('attributes', \array_values($attributes)); @@ -2545,8 +2550,14 @@ public function deleteRelationship(string $collection, string $id): bool $relatedCollection->setAttribute('attributes', \array_values($relatedAttributes)); $this->silent(function () use ($collection, $relatedCollection, $type, $twoWay, $id, $twoWayKey, $side) { - $this->updateDocument(self::METADATA, $collection->getId(), $collection); - $this->updateDocument(self::METADATA, $relatedCollection->getId(), $relatedCollection); + try { + $this->withTransaction(function () use ($collection, $relatedCollection) { + $this->updateDocument(self::METADATA, $collection->getId(), $collection); + $this->updateDocument(self::METADATA, $relatedCollection->getId(), $relatedCollection); + }); + } catch (\Throwable $e) { + throw new DatabaseException('Failed to delete relationship: ' . $e->getMessage()); + } $indexKey = '_index_' . $id; $twoWayIndexKey = '_index_' . $twoWayKey; @@ -2992,35 +3003,9 @@ public function getDocument(string $collection, string $id, array $queries = [], $attribute['type'] === Database::VAR_RELATIONSHIP ); - $hasTwoWayRelationship = false; - foreach ($relationships as $relationship) { - if ($relationship['options']['twoWay']) { - $hasTwoWayRelationship = true; - break; - } - } - - /** - * Bug with function purity in PHPStan means it thinks $this->map is always empty - * @phpstan-ignore-next-line - */ - foreach ($this->map as $key => $value) { - [$k, $v] = \explode('=>', $key); - $ck = $this->cacheName . '-cache-' . $this->getNamespace() . ':' . $this->adapter->getTenant() . ':map:' . $k; - $cache = $this->cache->load($ck, self::TTL, $ck); - if (empty($cache)) { - $cache = []; - } - if (!\in_array($v, $cache)) { - $cache[] = $v; - $this->cache->save($ck, $cache, $ck); - } - } - // Don't save to cache if it's part of a relationship - if (!$hasTwoWayRelationship && empty($relationships)) { + if (empty($relationships)) { $this->cache->save($documentCacheKey, $document->getArrayCopy(), $documentCacheHash); - // Add document reference to the collection key $this->cache->save($collectionCacheKey, 'empty', $documentCacheKey); } @@ -3948,7 +3933,6 @@ public function updateDocument(string $collection, string $id, Document $documen $document = $this->decode($collection, $document); - $this->purgeRelatedDocuments($collection, $id); $this->purgeCachedDocument($collection->getId(), $id); $this->trigger(self::EVENT_DOCUMENT_UPDATE, $document); @@ -4103,7 +4087,6 @@ public function updateDocuments(string $collection, Document $updates, array $qu } foreach ($documents as $document) { - $this->purgeRelatedDocuments($collection, $document->getId()); $this->purgeCachedDocument($collection->getId(), $document->getId()); } @@ -4752,7 +4735,6 @@ public function deleteDocument(string $collection, string $id): bool return $this->adapter->deleteDocument($collection->getId(), $id); }); - $this->purgeRelatedDocuments($collection, $id); $this->purgeCachedDocument($collection->getId(), $id); $this->trigger(self::EVENT_DOCUMENT_DELETE, $document); @@ -5246,7 +5228,6 @@ public function deleteDocuments(string $collection, array $queries = [], int $ba throw new ConflictException('Document was updated after the request timestamp'); } - $this->purgeRelatedDocuments($collection, $document->getId()); $this->purgeCachedDocument($collection->getId(), $document->getId()); } @@ -5292,6 +5273,8 @@ public function purgeCachedCollection(string $collectionId): bool $this->cache->purge($documentKey); } + $this->cache->purge($collectionKey); + return true; } @@ -5993,39 +5976,6 @@ public static function convertQueries(Document $collection, array $queries): arr return $queries; } - /** - * @param Document $collection - * @param string $id - * @return void - * @throws DatabaseException - */ - private function purgeRelatedDocuments(Document $collection, string $id): void - { - if ($collection->getId() === self::METADATA) { - return; - } - - $relationships = \array_filter( - $collection->getAttribute('attributes', []), - fn ($attribute) => - $attribute['type'] === Database::VAR_RELATIONSHIP - ); - - if (empty($relationships)) { - return; - } - - $key = $this->cacheName . '-cache-' . $this->getNamespace() . ':map:' . $collection->getId() . ':' . $id; - $cache = $this->cache->load($key, self::TTL, $key); - if (!empty($cache)) { - foreach ($cache as $v) { - list($collectionId, $documentId) = explode(':', $v); - $this->purgeCachedDocument($collectionId, $documentId); - } - $this->cache->purge($key); - } - } - /** * @return array> */ diff --git a/tests/e2e/Adapter/Base.php b/tests/e2e/Adapter/Base.php index ad117acd7..d5d172291 100644 --- a/tests/e2e/Adapter/Base.php +++ b/tests/e2e/Adapter/Base.php @@ -7897,7 +7897,7 @@ public function testIdenticalTwoWayKeyRelationship(): void ); $this->fail('Failed to throw Exception'); } catch (Exception $e) { - $this->assertEquals('Attribute already exists', $e->getMessage()); + $this->assertEquals('Relationship already exists', $e->getMessage()); } try { @@ -12955,10 +12955,12 @@ public function testDeleteMissingRelationship(): void return; } - $this->expectException(Exception::class); - $this->expectExceptionMessage('Attribute not found'); - - static::getDatabase()->deleteRelationship('test', 'test2'); + try { + static::getDatabase()->deleteRelationship('test', 'test2'); + $this->fail('Failed to throw exception'); + } catch (\Throwable $e) { + $this->assertEquals('Relationship not found', $e->getMessage()); + } } public function testCreateInvalidIntValueRelationship(): void @@ -13321,7 +13323,7 @@ public function testUpdateRelationshipToExistingKey(): void static::getDatabase()->updateRelationship('ovens', 'cakes', newKey: 'owner'); $this->fail('Failed to throw exception'); } catch (DuplicateException $e) { - $this->assertEquals('Attribute already exists', $e->getMessage()); + $this->assertEquals('Relationship already exists', $e->getMessage()); } try {