diff --git a/composer.lock b/composer.lock index 8f3d208b3..e2fa0a161 100644 --- a/composer.lock +++ b/composer.lock @@ -513,16 +513,16 @@ }, { "name": "laravel/pint", - "version": "v1.13.5", + "version": "v1.13.6", "source": { "type": "git", "url": "https://github.com/laravel/pint.git", - "reference": "df105cf8ce7a8f0b8a9425ff45cd281a5448e423" + "reference": "3e3d2ab01c7d8b484c18e6100ecf53639c744fa7" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laravel/pint/zipball/df105cf8ce7a8f0b8a9425ff45cd281a5448e423", - "reference": "df105cf8ce7a8f0b8a9425ff45cd281a5448e423", + "url": "https://api.github.com/repos/laravel/pint/zipball/3e3d2ab01c7d8b484c18e6100ecf53639c744fa7", + "reference": "3e3d2ab01c7d8b484c18e6100ecf53639c744fa7", "shasum": "" }, "require": { @@ -533,13 +533,13 @@ "php": "^8.1.0" }, "require-dev": { - "friendsofphp/php-cs-fixer": "^3.34.1", - "illuminate/view": "^10.26.2", - "laravel-zero/framework": "^10.1.2", + "friendsofphp/php-cs-fixer": "^3.38.0", + "illuminate/view": "^10.30.1", + "laravel-zero/framework": "^10.3.0", "mockery/mockery": "^1.6.6", "nunomaduro/larastan": "^2.6.4", "nunomaduro/termwind": "^1.15.1", - "pestphp/pest": "^2.20.0" + "pestphp/pest": "^2.24.2" }, "bin": [ "builds/pint" @@ -575,7 +575,7 @@ "issues": "https://github.com/laravel/pint/issues", "source": "https://github.com/laravel/pint" }, - "time": "2023-10-26T09:26:10+00:00" + "time": "2023-11-07T17:59:57+00:00" }, { "name": "myclabs/deep-copy", @@ -839,16 +839,16 @@ }, { "name": "phpstan/phpstan", - "version": "1.10.40", + "version": "1.10.44", "source": { "type": "git", "url": "https://github.com/phpstan/phpstan.git", - "reference": "93c84b5bf7669920d823631e39904d69b9c7dc5d" + "reference": "bf84367c53a23f759513985c54ffe0d0c249825b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpstan/zipball/93c84b5bf7669920d823631e39904d69b9c7dc5d", - "reference": "93c84b5bf7669920d823631e39904d69b9c7dc5d", + "url": "https://api.github.com/repos/phpstan/phpstan/zipball/bf84367c53a23f759513985c54ffe0d0c249825b", + "reference": "bf84367c53a23f759513985c54ffe0d0c249825b", "shasum": "" }, "require": { @@ -897,7 +897,7 @@ "type": "tidelift" } ], - "time": "2023-10-30T14:48:31+00:00" + "time": "2023-11-21T16:30:46+00:00" }, { "name": "phpunit/php-code-coverage", @@ -2428,7 +2428,7 @@ }, { "name": "symfony/deprecation-contracts", - "version": "v3.3.0", + "version": "v3.4.0", "source": { "type": "git", "url": "https://github.com/symfony/deprecation-contracts.git", @@ -2475,7 +2475,7 @@ "description": "A generic function and convention to trigger deprecation notices", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/deprecation-contracts/tree/v3.3.0" + "source": "https://github.com/symfony/deprecation-contracts/tree/v3.4.0" }, "funding": [ { @@ -2495,16 +2495,16 @@ }, { "name": "theseer/tokenizer", - "version": "1.2.1", + "version": "1.2.2", "source": { "type": "git", "url": "https://github.com/theseer/tokenizer.git", - "reference": "34a41e998c2183e22995f158c581e7b5e755ab9e" + "reference": "b2ad5003ca10d4ee50a12da31de12a5774ba6b96" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/theseer/tokenizer/zipball/34a41e998c2183e22995f158c581e7b5e755ab9e", - "reference": "34a41e998c2183e22995f158c581e7b5e755ab9e", + "url": "https://api.github.com/repos/theseer/tokenizer/zipball/b2ad5003ca10d4ee50a12da31de12a5774ba6b96", + "reference": "b2ad5003ca10d4ee50a12da31de12a5774ba6b96", "shasum": "" }, "require": { @@ -2533,7 +2533,7 @@ "description": "A small library for converting tokenized PHP source code into XML and potentially other formats", "support": { "issues": "https://github.com/theseer/tokenizer/issues", - "source": "https://github.com/theseer/tokenizer/tree/1.2.1" + "source": "https://github.com/theseer/tokenizer/tree/1.2.2" }, "funding": [ { @@ -2541,7 +2541,7 @@ "type": "github" } ], - "time": "2021-07-28T10:34:58+00:00" + "time": "2023-11-20T00:12:19+00:00" }, { "name": "utopia-php/cli", @@ -2608,5 +2608,5 @@ "php": ">=8.0" }, "platform-dev": [], - "plugin-api-version": "2.2.0" + "plugin-api-version": "2.6.0" } diff --git a/src/Database/Database.php b/src/Database/Database.php index f6bd3cdec..187802d8c 100644 --- a/src/Database/Database.php +++ b/src/Database/Database.php @@ -136,6 +136,8 @@ class Database protected Cache $cache; + protected string $name; + /** * @var array */ @@ -297,6 +299,8 @@ class Database protected bool $resolveRelationships = true; + protected bool $filter = true; + private int $relationshipFetchDepth = 1; /** @@ -320,11 +324,12 @@ class Database * @param Cache $cache * @param array $filters */ - public function __construct(Adapter $adapter, Cache $cache, array $filters = []) + public function __construct(Adapter $adapter, Cache $cache, array $filters = [], string $name = 'default') { $this->adapter = $adapter; $this->cache = $cache; $this->instanceFilters = $filters; + $this->name = $name; self::addFilter( 'json', @@ -643,6 +648,30 @@ public function clearTimeout(string $event = Database::EVENT_ALL): void $this->adapter->clearTimeout($event); } + /** + * Enable filters + * + * @return $this + */ + public function enableFilters(): self + { + $this->filter = true; + + return $this; + } + + /** + * Disable filters + * + * @return $this + */ + public function disableFilters(): self + { + $this->filter = false; + + return $this; + } + /** * Ping Database * @@ -2323,7 +2352,7 @@ public function getDocument(string $collection, string $id, array $queries = []) $validator = new Authorization(self::PERMISSION_READ); $documentSecurity = $collection->getAttribute('documentSecurity', false); - $cacheKey = 'cache-' . $this->getNamespace() . ':' . $collection->getId() . ':' . $id; + $cacheKey = $this->name . '-cache-' . $this->getNamespace() . ':' . $collection->getId() . ':' . $id; if (!empty($selections)) { $cacheKey .= ':' . \md5(\implode($selections)); @@ -2394,7 +2423,7 @@ public function getDocument(string $collection, string $id, array $queries = []) */ foreach ($this->map as $key => $value) { list($k, $v) = explode('=>', $key); - $ck = 'cache-' . $this->getNamespace() . ':map:' . $k; + $ck = $this->name . '-cache-' . $this->getNamespace() . ':map:' . $k; $cache = $this->cache->load($ck, self::TTL); if (empty($cache)) { $cache = []; @@ -2667,6 +2696,7 @@ private function populateDocumentRelationships(Document $collection, Document $d * * @param string $collection * @param Document $document + * @param bool $preserveDates If true, createdAt and updatedAt will not be overwritten * * @return Document * @@ -2674,7 +2704,7 @@ private function populateDocumentRelationships(Document $collection, Document $d * @throws DatabaseException * @throws StructureException */ - public function createDocument(string $collection, Document $document): Document + public function createDocument(string $collection, Document $document, bool $preserveDates = false): Document { $collection = $this->silent(fn () => $this->getCollection($collection)); @@ -2687,11 +2717,14 @@ public function createDocument(string $collection, Document $document): Document $time = DateTime::now(); + $createdAt = $document->getCreatedAt(); + $updatedAt = $document->getUpdatedAt(); + $document ->setAttribute('$id', empty($document->getId()) ? ID::unique() : $document->getId()) ->setAttribute('$collection', $collection->getId()) - ->setAttribute('$createdAt', $time) - ->setAttribute('$updatedAt', $time); + ->setAttribute('$createdAt', empty($createdAt) || !$preserveDates ? $time : $createdAt) + ->setAttribute('$updatedAt', empty($updatedAt) || !$preserveDates ? $time : $updatedAt); $document = $this->encode($collection, $document); @@ -2700,10 +2733,10 @@ public function createDocument(string $collection, Document $document): Document throw new InvalidArgumentException($validator->getDescription()); } - $structure = new Structure($collection); - if (!$structure->isValid($document)) { - throw new StructureException($structure->getDescription()); - } + // $structure = new Structure($collection); + // if (!$structure->isValid($document)) { + // throw new StructureException($structure->getDescription()); + // } if ($this->resolveRelationships) { $document = $this->silent(fn () => $this->createDocumentRelationships($collection, $document)); @@ -2728,6 +2761,7 @@ public function createDocument(string $collection, Document $document): Document * @param string $collection * @param array $documents * @param int $batchSize + * @param bool $preserveDates If true, createdAt and updatedAt will not be overwritten * * @return array * @@ -2735,7 +2769,7 @@ public function createDocument(string $collection, Document $document): Document * @throws StructureException * @throws Exception */ - public function createDocuments(string $collection, array $documents, int $batchSize = self::INSERT_BATCH_SIZE): array + public function createDocuments(string $collection, array $documents, int $batchSize = self::INSERT_BATCH_SIZE, bool $preserveDates = false): array { if (empty($documents)) { return []; @@ -2746,18 +2780,21 @@ public function createDocuments(string $collection, array $documents, int $batch $time = DateTime::now(); foreach ($documents as $key => $document) { + $createdAt = $document->getCreatedAt(); + $updatedAt = $document->getUpdatedAt(); + $document ->setAttribute('$id', empty($document->getId()) ? ID::unique() : $document->getId()) ->setAttribute('$collection', $collection->getId()) - ->setAttribute('$createdAt', $time) - ->setAttribute('$updatedAt', $time); + ->setAttribute('$createdAt', empty($createdAt) || !$preserveDates ? $time : $createdAt) + ->setAttribute('$updatedAt', empty($updatedAt) || !$preserveDates ? $time : $updatedAt); $document = $this->encode($collection, $document); - $validator = new Structure($collection); - if (!$validator->isValid($document)) { - throw new StructureException($validator->getDescription()); - } + // $validator = new Structure($collection); + // if (!$validator->isValid($document)) { + // throw new StructureException($validator->getDescription()); + // } $documents[$key] = $document; } @@ -3055,6 +3092,7 @@ private function relateDocumentsById( * @param string $collection * @param string $id * @param Document $document + * @param bool $preserveDates If true, updatedAt will not be overwritten * @return Document * * @throws AuthorizationException @@ -3062,7 +3100,7 @@ private function relateDocumentsById( * @throws DatabaseException * @throws StructureException */ - public function updateDocument(string $collection, string $id, Document $document): Document + public function updateDocument(string $collection, string $id, Document $document, bool $preserveDates = false): Document { if (!$document->getId() || !$id) { throw new DatabaseException('Must define $id attribute'); @@ -3176,7 +3214,8 @@ public function updateDocument(string $collection, string $id, Document $documen } if ($shouldUpdate) { - $document->setAttribute('$updatedAt', $time); + $updatedAt = $document->getUpdatedAt(); + $document->setAttribute('$updatedAt', empty($updatedAt) || !$preserveDates ? $time : $updatedAt); } // Check if document was updated after the request timestamp @@ -3207,7 +3246,7 @@ public function updateDocument(string $collection, string $id, Document $documen $this->purgeRelatedDocuments($collection, $id); - $this->cache->purge('cache-' . $this->getNamespace() . ':' . $collection->getId() . ':' . $id . ':*'); + $this->cache->purge($this->name . '-cache-' . $this->getNamespace() . ':' . $collection->getId() . ':' . $id . ':*'); $this->trigger(self::EVENT_DOCUMENT_UPDATE, $document); @@ -3220,6 +3259,7 @@ public function updateDocument(string $collection, string $id, Document $documen * @param string $collection * @param array $documents * @param int $batchSize + * @param bool $preserveDates If true, updatedAt will not be overwritten * * @return array * @@ -3227,7 +3267,7 @@ public function updateDocument(string $collection, string $id, Document $documen * @throws Exception * @throws StructureException */ - public function updateDocuments(string $collection, array $documents, int $batchSize = self::INSERT_BATCH_SIZE): array + public function updateDocuments(string $collection, array $documents, int $batchSize = self::INSERT_BATCH_SIZE, bool $preserveDates = false): array { if (empty($documents)) { return []; @@ -3241,7 +3281,8 @@ public function updateDocuments(string $collection, array $documents, int $batch throw new Exception('Must define $id attribute for each document'); } - $document->setAttribute('$updatedAt', $time); + $updatedAt = $document->getUpdatedAt(); + $document->setAttribute('$updatedAt', empty($updatedAt) || !$preserveDates ? $time : $updatedAt); $document = $this->encode($collection, $document); $old = Authorization::skip(fn () => $this->silent( @@ -3715,7 +3756,7 @@ public function increaseDocumentAttribute(string $collection, string $id, string $max = $max ? $max - $value : null; $result = $this->adapter->increaseDocumentAttribute($collection->getId(), $id, $attribute, $value, null, $max); - $this->cache->purge('cache-' . $this->getNamespace() . ':' . $collection->getId() . ':' . $id . ':*'); + $this->cache->purge($this->name . '-cache-' . $this->getNamespace() . ':' . $collection->getId() . ':' . $id . ':*'); $this->trigger(self::EVENT_DOCUMENT_INCREASE, $document); @@ -3782,7 +3823,7 @@ public function decreaseDocumentAttribute(string $collection, string $id, string $min = $min ? $min + $value : null; $result = $this->adapter->increaseDocumentAttribute($collection->getId(), $id, $attribute, $value * -1, $min); - $this->cache->purge('cache-' . $this->getNamespace() . ':' . $collection->getId() . ':' . $id . ':*'); + $this->cache->purge($this->name . '-cache-' . $this->getNamespace() . ':' . $collection->getId() . ':' . $id . ':*'); $this->trigger(self::EVENT_DOCUMENT_DECREASE, $document); return $result; @@ -3837,7 +3878,7 @@ public function deleteDocument(string $collection, string $id): bool $deleted = $this->adapter->deleteDocument($collection->getId(), $id); $this->purgeRelatedDocuments($collection, $id); - $this->cache->purge('cache-' . $this->getNamespace() . ':' . $collection->getId() . ':' . $id . ':*'); + $this->cache->purge($this->name . '-cache-' . $this->getNamespace() . ':' . $collection->getId() . ':' . $id . ':*'); $this->trigger(self::EVENT_DOCUMENT_DELETE, $document); @@ -4223,7 +4264,7 @@ private function deleteCascade(Document $collection, Document $relatedCollection */ public function deleteCachedCollection(string $collection): bool { - return $this->cache->purge('cache-' . $this->getNamespace() . ':' . $collection . ':*'); + return $this->cache->purge($this->name . '-cache-' . $this->getNamespace() . ':' . $collection . ':*'); } /** @@ -4237,7 +4278,7 @@ public function deleteCachedCollection(string $collection): bool */ public function deleteCachedDocument(string $collection, string $id): bool { - return $this->cache->purge('cache-' . $this->getNamespace() . ':' . $collection . ':' . $id . ':*'); + return $this->cache->purge($this->name . '-cache-' . $this->getNamespace() . ':' . $collection . ':' . $id . ':*'); } /** @@ -4759,6 +4800,10 @@ protected function encodeAttribute(string $name, $value, Document $document): mi */ protected function decodeAttribute(string $name, mixed $value, Document $document): mixed { + if(!$this->filter) { + return $value; + } + if (!array_key_exists($name, self::$filters) && !array_key_exists($name, $this->instanceFilters)) { throw new DatabaseException('Filter not found'); } @@ -4942,7 +4987,7 @@ private function purgeRelatedDocuments(Document $collection, string $id): void return; } - $key = 'cache-' . $this->getNamespace() . ':map:' . $collection->getId() . ':' . $id; + $key = $this->name . '-cache-' . $this->getNamespace() . ':map:' . $collection->getId() . ':' . $id; $cache = $this->cache->load($key, self::TTL); if (!empty($cache)) { foreach ($cache as $v) { diff --git a/tests/Database/Base.php b/tests/Database/Base.php index 55c5b9688..c2727ef59 100644 --- a/tests/Database/Base.php +++ b/tests/Database/Base.php @@ -75,6 +75,97 @@ public function testCreateExistsDelete(): void $this->assertEquals(true, static::getDatabase()->create()); } + public function testPreserveDatesUpdate(): void + { + Authorization::disable(); + + static::getDatabase()->createCollection('preserve_update_dates'); + + static::getDatabase()->createAttribute('preserve_update_dates', 'attr1', Database::VAR_STRING, 10, false); + + $doc1 = static::getDatabase()->createDocument('preserve_update_dates', new Document([ + '$id' => 'doc1', + '$permissions' => [], + 'attr1' => 'value1', + ])); + + $doc2 = static::getDatabase()->createDocument('preserve_update_dates', new Document([ + '$id' => 'doc2', + '$permissions' => [], + 'attr1' => 'value2', + ])); + + $doc3 = static::getDatabase()->createDocument('preserve_update_dates', new Document([ + '$id' => 'doc3', + '$permissions' => [], + 'attr1' => 'value3', + ])); + + $newDate = '2000-01-01T10:00:00.000+00:00'; + + $doc1->setAttribute('$updatedAt', $newDate); + static::getDatabase()->updateDocument('preserve_update_dates', 'doc1', $doc1, true); + $doc1 = static::getDatabase()->getDocument('preserve_update_dates', 'doc1'); + $this->assertEquals($newDate, $doc1->getAttribute('$updatedAt')); + + $doc2->setAttribute('$updatedAt', $newDate); + $doc3->setAttribute('$updatedAt', $newDate); + static::getDatabase()->updateDocuments('preserve_update_dates', [$doc2, $doc3], 2, true); + + $doc2 = static::getDatabase()->getDocument('preserve_update_dates', 'doc2'); + $doc3 = static::getDatabase()->getDocument('preserve_update_dates', 'doc3'); + $this->assertEquals($newDate, $doc2->getAttribute('$updatedAt')); + $this->assertEquals($newDate, $doc3->getAttribute('$updatedAt')); + + static::getDatabase()->deleteCollection('preserve_update_dates'); + + Authorization::reset(); + } + + public function testPreserveDatesCreate(): void + { + Authorization::disable(); + + static::getDatabase()->createCollection('preserve_create_dates'); + + static::getDatabase()->createAttribute('preserve_create_dates', 'attr1', Database::VAR_STRING, 10, false); + + $date = '2000-01-01T10:00:00.000+00:00'; + + static::getDatabase()->createDocument('preserve_create_dates', new Document([ + '$id' => 'doc1', + '$permissions' => [], + 'attr1' => 'value1', + '$createdAt' => $date + ]), true); + + static::getDatabase()->createDocuments('preserve_create_dates', [ + new Document([ + '$id' => 'doc2', + '$permissions' => [], + 'attr1' => 'value2', + '$createdAt' => $date + ]), + new Document([ + '$id' => 'doc3', + '$permissions' => [], + 'attr1' => 'value3', + '$createdAt' => $date + ]), + ], 2, true); + + $doc1 = static::getDatabase()->getDocument('preserve_create_dates', 'doc1'); + $doc2 = static::getDatabase()->getDocument('preserve_create_dates', 'doc2'); + $doc3 = static::getDatabase()->getDocument('preserve_create_dates', 'doc3'); + $this->assertEquals($date, $doc1->getAttribute('$createdAt')); + $this->assertEquals($date, $doc2->getAttribute('$createdAt')); + $this->assertEquals($date, $doc3->getAttribute('$createdAt')); + + static::getDatabase()->deleteCollection('preserve_create_dates'); + + Authorization::reset(); + } + /** * @throws Exception|Throwable */