diff --git a/src/Database/Database.php b/src/Database/Database.php index 144428665..4ffd68ed9 100644 --- a/src/Database/Database.php +++ b/src/Database/Database.php @@ -8554,7 +8554,7 @@ protected function purgeCachedDocumentInternal(string $collectionId, ?string $id return true; } - [$collectionKey, $documentKey] = $this->getCacheKeys($collectionId, $id); + [$collectionKey, $documentKey] = $this->getCacheBaseKeys($collectionId, $id); $this->cache->purge($collectionKey, $documentKey); $this->cache->purge($documentKey); @@ -9927,10 +9927,9 @@ public function getSchemaIndexes(string $collection): array /** * @param string $collectionId * @param string|null $documentId - * @param array $selects - * @return array{0: string, 1: string, 2: string} + * @return array{0: string, 1: string} */ - public function getCacheKeys(string $collectionId, ?string $documentId = null, array $selects = []): array + public function getCacheBaseKeys(string $collectionId, ?string $documentId = null): array { if ($this->adapter->getSupportForHostname()) { $hostname = $this->adapter->getHostname(); @@ -9955,9 +9954,20 @@ public function getCacheKeys(string $collectionId, ?string $documentId = null, a $collectionId ); - if ($documentId) { - $documentKey = $documentHashKey = "{$collectionKey}:{$documentId}"; + return [$collectionKey, $documentId ? "{$collectionKey}:{$documentId}" : '']; + } + /** + * @param string $collectionId + * @param string|null $documentId + * @param array $selects + * @return array{0: string, 1: string, 2: string} + */ + public function getCacheKeys(string $collectionId, ?string $documentId = null, array $selects = []): array + { + [$collectionKey, $documentKey] = $this->getCacheBaseKeys($collectionId, $documentId); + + if ($documentId) { $sortedSelects = $selects; \sort($sortedSelects); @@ -9971,7 +9981,7 @@ public function getCacheKeys(string $collectionId, ?string $documentId = null, a return [ $collectionKey, - $documentKey ?? '', + $documentKey, $documentHashKey ?? '' ]; } diff --git a/tests/unit/CacheKeyTest.php b/tests/unit/CacheKeyTest.php index 92c3bc608..aa9457362 100644 --- a/tests/unit/CacheKeyTest.php +++ b/tests/unit/CacheKeyTest.php @@ -33,6 +33,27 @@ private function getHashKey(Database $db, string $collection = 'col', string $do return $hashKey; } + public function testBaseKeysMatchScopedVariantKeys(): void + { + $adapter = $this->createMock(Adapter::class); + $adapter->method('getSupportForHostname')->willReturn(true); + $adapter->method('getHostname')->willReturn('mysql-project'); + $adapter->method('getNamespace')->willReturn('project'); + $adapter->method('getTenant')->willReturn(42); + $adapter->method('getSharedTables')->willReturn(true); + + $db = new Database($adapter, new Cache(new None())); + $db->setGlobalCollections(['global']); + + foreach ([['col', 'doc'], [Database::METADATA, 'col'], [Database::METADATA, 'global']] as [$collection, $document]) { + $full = $db->getCacheKeys($collection, $document, ['name']); + $withoutHash = $db->getCacheBaseKeys($collection, $document); + + $this->assertSame([$full[0], $full[1]], $withoutHash); + $this->assertNotSame('', $full[2]); + } + } + public function testSameConfigProducesSameCacheKey(): void { $db1 = $this->createDatabase(); diff --git a/tests/unit/ForUpdateCacheTest.php b/tests/unit/ForUpdateCacheTest.php index aef2736b3..c1297da86 100644 --- a/tests/unit/ForUpdateCacheTest.php +++ b/tests/unit/ForUpdateCacheTest.php @@ -53,6 +53,17 @@ private function staleCache(string $attribute, string $value): void $this->adapter->updateDocument($collection, 'project', $document, true); } + public function testPurgeMakesTheNextReadReturnFreshData(): void + { + $this->assertSame('stale', $this->database->getDocument('projects', 'project')->getAttribute('name')); + $this->staleCache('name', 'fresh'); + $this->assertSame('stale', $this->database->getDocument('projects', 'project')->getAttribute('name')); + + $this->database->purgeCachedDocument('projects', 'project'); + + $this->assertSame('fresh', $this->database->getDocument('projects', 'project')->getAttribute('name')); + } + public function testForUpdateReadBypassesStaleCache(): void { $cached = $this->database->getDocument('projects', 'project'); diff --git a/tests/unit/WithCacheLeaseTest.php b/tests/unit/WithCacheLeaseTest.php index 8eafd3540..c35682a90 100644 --- a/tests/unit/WithCacheLeaseTest.php +++ b/tests/unit/WithCacheLeaseTest.php @@ -42,6 +42,25 @@ protected function setUp(): void $this->key = $this->database->getQueryCacheKey('projects'); } + public function testDocumentPurgeRemovesAllVariantsAndRejectsStaleWrites(): void + { + [$collectionKey, $documentKey, $firstHash] = $this->database->getCacheKeys('projects', 'project'); + [, , $secondHash] = $this->database->getCacheKeys('projects', 'project', ['name']); + $this->cacheAdapter->save($collectionKey, 'legacy', $documentKey); + $this->cacheAdapter->save($documentKey, 'first', $firstHash); + $this->cacheAdapter->save($documentKey, 'second', $secondHash); + $lease = $this->cacheAdapter->getGeneration($documentKey); + + $this->assertTrue($this->database->purgeCachedDocument('projects', 'project')); + + $this->assertFalse($this->cacheAdapter->load($collectionKey, Database::TTL, $documentKey)); + $this->assertFalse($this->cacheAdapter->load($documentKey, Database::TTL, $firstHash)); + $this->assertFalse($this->cacheAdapter->load($documentKey, Database::TTL, $secondHash)); + $this->cacheAdapter->saveWithLease($documentKey, 'stale', $firstHash, $lease); + $this->assertFalse($this->cacheAdapter->load($documentKey, Database::TTL, $firstHash)); + $this->assertSame('fresh', $this->database->getDocument('projects', 'project')->getAttribute('name')); + } + public function testStaleListWriteAfterConcurrentPurgeIsRejected(): void { $hash = 'list-hash';