From 4d6fb4c5d90bf25dd6b1736928ddc52466959b6d Mon Sep 17 00:00:00 2001 From: Bradley Schofield Date: Fri, 6 Dec 2024 13:08:11 +0900 Subject: [PATCH 1/3] Respect timestamps in bulk operations and add tests --- src/Database/Database.php | 17 +++++++++++++++++ tests/e2e/Adapter/Base.php | 28 ++++++++++++++++++++++++++++ 2 files changed, 45 insertions(+) diff --git a/src/Database/Database.php b/src/Database/Database.php index 95a499df2..38c0e8942 100644 --- a/src/Database/Database.php +++ b/src/Database/Database.php @@ -4084,6 +4084,12 @@ public function updateDocuments(string $collection, Document $updates, array $qu $this->silent(fn () => $this->updateDocumentRelationships($collection, $document, $newDocument)); $documents[] = $newDocument; } + + // Check if document was updated after the request timestamp + $oldUpdatedAt = new \DateTime($document->getUpdatedAt()); + if (!is_null($this->timestamp) && $oldUpdatedAt > $this->timestamp) { + throw new ConflictException('Document was updated after the request timestamp'); + } } $getResults = fn () => $this->adapter->updateDocuments( @@ -5236,6 +5242,17 @@ public function deleteDocuments(string $collection, array $queries = [], int $ba $document = $this->silent(fn () => $this->deleteDocumentRelationships($collection, $document)); } + // Check if document was updated after the request timestamp + try { + $oldUpdatedAt = new \DateTime($document->getUpdatedAt()); + } catch (Exception $e) { + throw new DatabaseException($e->getMessage(), $e->getCode(), $e); + } + + if (!\is_null($this->timestamp) && $oldUpdatedAt > $this->timestamp) { + throw new ConflictException('Document was updated after the request timestamp'); + } + $this->purgeRelatedDocuments($collection, $document->getId()); $this->purgeCachedDocument($collection->getId(), $document->getId()); } diff --git a/tests/e2e/Adapter/Base.php b/tests/e2e/Adapter/Base.php index 943eb458e..9272cd002 100644 --- a/tests/e2e/Adapter/Base.php +++ b/tests/e2e/Adapter/Base.php @@ -2110,6 +2110,8 @@ public function testCreateDocuments(): array $this->assertEquals(9223372036854775807, $document->getAttribute('bigint')); } + // Test (FAIL) create documents with invalid timestamps + return $documents; } @@ -15917,6 +15919,18 @@ public function testDeleteBulkDocuments(): void $docs = static::getDatabase()->find('bulk_delete'); $this->assertCount(5, $docs); + // TEST (FAIL): Can't delete documents in the past + $oneHourAgo = (new \DateTime())->sub(new \DateInterval('PT1H')); + + try { + $this->getDatabase()->withRequestTimestamp($oneHourAgo, function () use ($document) { + return $this->getDatabase()->deleteDocuments($document->getCollection()); + }); + $this->fail('Failed to throw exception'); + } catch (ConflictException $e) { + $this->assertEquals('Document was updated after the request timestamp', $e->getMessage()); + } + // TEST (FAIL): Bulk delete all documents with invalid collection permission static::getDatabase()->updateCollection('bulk_delete', [], false); try { @@ -16650,6 +16664,20 @@ public function testUpdateDocuments(): void $this->assertEquals('text📝 updated all', $document->getAttribute('string')); } + // TEST: Can't delete documents in the past + $oneHourAgo = (new \DateTime())->sub(new \DateInterval('PT1H')); + + try { + $this->getDatabase()->withRequestTimestamp($oneHourAgo, function () use ($collection) { + return static::getDatabase()->updateDocuments($collection, new Document([ + 'string' => 'text📝 updated all', + ])); + }); + $this->fail('Failed to throw exception'); + } catch (ConflictException $e) { + $this->assertEquals('Document was updated after the request timestamp', $e->getMessage()); + } + // Check collection level permissions static::getDatabase()->updateCollection($collection, permissions: [ Permission::read(Role::user('asd')), From 6d880506ef516baa295eae170b611b4cccb9e87c Mon Sep 17 00:00:00 2001 From: Bradley Schofield Date: Fri, 6 Dec 2024 13:12:39 +0900 Subject: [PATCH 2/3] Fix PHPStan --- tests/e2e/Adapter/Base.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/e2e/Adapter/Base.php b/tests/e2e/Adapter/Base.php index 9272cd002..2ac1fdf14 100644 --- a/tests/e2e/Adapter/Base.php +++ b/tests/e2e/Adapter/Base.php @@ -15923,8 +15923,8 @@ public function testDeleteBulkDocuments(): void $oneHourAgo = (new \DateTime())->sub(new \DateInterval('PT1H')); try { - $this->getDatabase()->withRequestTimestamp($oneHourAgo, function () use ($document) { - return $this->getDatabase()->deleteDocuments($document->getCollection()); + $this->getDatabase()->withRequestTimestamp($oneHourAgo, function () { + return $this->getDatabase()->deleteDocuments('bulk_delete'); }); $this->fail('Failed to throw exception'); } catch (ConflictException $e) { From 08e2b395114f9b97303a60c89e5beb7674075536 Mon Sep 17 00:00:00 2001 From: Bradley Schofield Date: Mon, 9 Dec 2024 12:21:39 +0900 Subject: [PATCH 3/3] Address Comments --- composer.json | 6 ++++++ src/Database/Database.php | 7 ++++++- tests/e2e/Adapter/Base.php | 2 -- 3 files changed, 12 insertions(+), 3 deletions(-) diff --git a/composer.json b/composer.json index 66b0fad6d..71c11264f 100755 --- a/composer.json +++ b/composer.json @@ -55,5 +55,11 @@ "ext-redis": "Needed to support Redis Cache Adapter", "ext-pdo": "Needed to support MariaDB, MySQL or SQLite Database Adapter", "mongodb/mongodb": "Needed to support MongoDB Database Adapter" + }, + "config": { + "allow-plugins": { + "php-http/discovery": false, + "tbachert/spi": false + } } } diff --git a/src/Database/Database.php b/src/Database/Database.php index 38c0e8942..551e318e2 100644 --- a/src/Database/Database.php +++ b/src/Database/Database.php @@ -4086,7 +4086,12 @@ public function updateDocuments(string $collection, Document $updates, array $qu } // Check if document was updated after the request timestamp - $oldUpdatedAt = new \DateTime($document->getUpdatedAt()); + try { + $oldUpdatedAt = new \DateTime($document->getUpdatedAt()); + } catch (Exception $e) { + throw new DatabaseException($e->getMessage(), $e->getCode(), $e); + } + if (!is_null($this->timestamp) && $oldUpdatedAt > $this->timestamp) { throw new ConflictException('Document was updated after the request timestamp'); } diff --git a/tests/e2e/Adapter/Base.php b/tests/e2e/Adapter/Base.php index 2ac1fdf14..602241299 100644 --- a/tests/e2e/Adapter/Base.php +++ b/tests/e2e/Adapter/Base.php @@ -2110,8 +2110,6 @@ public function testCreateDocuments(): array $this->assertEquals(9223372036854775807, $document->getAttribute('bigint')); } - // Test (FAIL) create documents with invalid timestamps - return $documents; }