From 52865b922f726968d25984651aa6f34c347329c4 Mon Sep 17 00:00:00 2001 From: Prem Palanisamy Date: Tue, 7 Jul 2026 08:41:08 +0100 Subject: [PATCH 1/2] Make query cache helpers private --- src/Database/Database.php | 6 +-- tests/unit/CacheKeyTest.php | 58 ++++++++++++++++++++--------- tests/unit/QueryCacheTest.php | 61 +++++++++++++++++++++++-------- tests/unit/WithCacheLeaseTest.php | 9 ++++- 4 files changed, 97 insertions(+), 37 deletions(-) diff --git a/src/Database/Database.php b/src/Database/Database.php index 74e75d883..44d712f8b 100644 --- a/src/Database/Database.php +++ b/src/Database/Database.php @@ -8584,7 +8584,7 @@ public function find(string $collection, array $queries = [], string $forPermiss * @param string|null $namespace * @return bool */ - public function purgeCachedQueries(string $collection, ?string $namespace = null): bool + private function purgeCachedQueries(string $collection, ?string $namespace = null): bool { $collectionDocument = $this->silent(fn () => $this->getCollection($collection)); $collection = $collectionDocument->isEmpty() ? $collection : $collectionDocument->getId(); @@ -9719,7 +9719,7 @@ public function getCacheKeys(string $collectionId, ?string $documentId = null, a * @param string|null $namespace * @return string */ - public function getQueryCacheKey(string $collectionId, ?string $namespace = null): string + private function getQueryCacheKey(string $collectionId, ?string $namespace = null): string { $hostname = $this->adapter->getSupportForHostname() ? $this->adapter->getHostname() @@ -9744,7 +9744,7 @@ public function getQueryCacheKey(string $collectionId, ?string $namespace = null * @param string $forPermission * @return string|null */ - public function getQueryCacheField( + private function getQueryCacheField( ?Document $collection = null, array $queries = [], string $field = 'documents', diff --git a/tests/unit/CacheKeyTest.php b/tests/unit/CacheKeyTest.php index 92c3bc608..be9eac583 100644 --- a/tests/unit/CacheKeyTest.php +++ b/tests/unit/CacheKeyTest.php @@ -33,6 +33,28 @@ private function getHashKey(Database $db, string $collection = 'col', string $do return $hashKey; } + private function getQueryCacheKey(Database $db, string $collectionId, ?string $namespace = null): string + { + $method = new \ReflectionMethod(Database::class, 'getQueryCacheKey'); + + return $method->invoke($db, $collectionId, $namespace); + } + + /** + * @param array $queries + */ + private function getQueryCacheField( + Database $db, + ?Document $collection = null, + array $queries = [], + string $field = 'documents', + string $forPermission = Database::PERMISSION_READ, + ): ?string { + $method = new \ReflectionMethod(Database::class, 'getQueryCacheField'); + + return $method->invoke($db, $collection, $queries, $field, $forPermission); + } + public function testSameConfigProducesSameCacheKey(): void { $db1 = $this->createDatabase(); @@ -147,7 +169,7 @@ public function testQueryCacheKeyUsesQueryCacheShape(): void $this->assertSame( 'default-cache-mysql-console:_39::collection:ttl_cache_table:query', - $db->getQueryCacheKey('ttl_cache_table'), + $this->getQueryCacheKey($db, 'ttl_cache_table'), ); } @@ -163,7 +185,7 @@ public function testQueryCacheKeyCanOverrideNamespaceSegment(): void $this->assertSame( 'default-cache-mysql-console:_39::collection:wafrules:query', - $db->getQueryCacheKey('wafrules', '_39'), + $this->getQueryCacheKey($db, 'wafrules', '_39'), ); } @@ -192,7 +214,7 @@ public function testQueryCacheFieldUsesQueryCacheShape(): void . (\json_encode($collection->getAttribute('$permissions', [])) ?: '') . (\json_encode($collection->getAttribute('documentSecurity', false)) ?: '') ); - $field = $db->getQueryCacheField($collection, $queries); + $field = $this->getQueryCacheField($db, $collection, $queries); $this->assertStringStartsWith("{$schemaHash}:", $field); $this->assertStringEndsWith(':documents', $field); @@ -203,7 +225,8 @@ public function testQueryCacheFieldChangesWithInputs(): void { $db = $this->createDatabase(); - $field = $db->getQueryCacheField( + $field = $this->getQueryCacheField( + $db, new Document([ 'attributes' => [new Document(['$id' => 'name', 'type' => Database::VAR_STRING])], 'indexes' => [], @@ -213,7 +236,8 @@ public function testQueryCacheFieldChangesWithInputs(): void $this->assertNotSame( $field, - $db->getQueryCacheField( + $this->getQueryCacheField( + $db, new Document([ 'attributes' => [new Document(['$id' => 'status', 'type' => Database::VAR_STRING])], 'indexes' => [], @@ -221,26 +245,26 @@ public function testQueryCacheFieldChangesWithInputs(): void [Query::limit(10)], ), ); - $this->assertNotSame($field, $db->getQueryCacheField(null, [Query::limit(20)])); - $this->assertStringEndsWith(':total', $db->getQueryCacheField(null, [Query::limit(10)], 'total')); + $this->assertNotSame($field, $this->getQueryCacheField($db, null, [Query::limit(20)])); + $this->assertStringEndsWith(':total', $this->getQueryCacheField($db, null, [Query::limit(10)], 'total')); } public function testQueryCacheFieldChangesWithActiveAuthorizationContext(): void { $db = $this->createDatabase(); - $field = $db->getQueryCacheField(null, [Query::limit(10)]); + $field = $this->getQueryCacheField($db, null, [Query::limit(10)]); $this->assertNotSame( $field, - $db->getAuthorization()->skip(fn () => $db->getQueryCacheField(null, [Query::limit(10)])), + $db->getAuthorization()->skip(fn () => $this->getQueryCacheField($db, null, [Query::limit(10)])), ); $db->getAuthorization()->addRole('user:1'); $this->assertNotSame( $field, - $db->getQueryCacheField(null, [Query::limit(10)]), + $this->getQueryCacheField($db, null, [Query::limit(10)]), ); } @@ -248,21 +272,21 @@ public function testQueryCacheFieldReturnsNullForNonReadPermission(): void { $db = $this->createDatabase(); - $this->assertNull($db->getQueryCacheField(forPermission: Database::PERMISSION_UPDATE)); + $this->assertNull($this->getQueryCacheField($db, forPermission: Database::PERMISSION_UPDATE)); } public function testQueryCacheFieldIncludesCursorDocumentPayload(): void { $db = $this->createDatabase(); - $fieldA = $db->getQueryCacheField(null, [ + $fieldA = $this->getQueryCacheField($db, null, [ Query::orderAsc('name'), Query::cursorAfter(new Document([ '$id' => 'cursor', 'name' => 'alpha', ])), ]); - $fieldB = $db->getQueryCacheField(null, [ + $fieldB = $this->getQueryCacheField($db, null, [ Query::orderAsc('name'), Query::cursorAfter(new Document([ '$id' => 'cursor', @@ -277,15 +301,15 @@ public function testQueryCacheFieldIncludesAmbientState(): void { $db = $this->createDatabase(); - $field = $db->getQueryCacheField(null, [Query::limit(10)]); + $field = $this->getQueryCacheField($db, null, [Query::limit(10)]); $this->assertNotSame( $field, - $db->skipFilters(fn () => $db->getQueryCacheField(null, [Query::limit(10)]), ['json']), + $db->skipFilters(fn () => $this->getQueryCacheField($db, null, [Query::limit(10)]), ['json']), ); $this->assertNotSame( $field, - $db->skipRelationships(fn () => $db->getQueryCacheField(null, [Query::limit(10)])), + $db->skipRelationships(fn () => $this->getQueryCacheField($db, null, [Query::limit(10)])), ); } @@ -297,7 +321,7 @@ public function testQueryCacheFieldValidatesQueryTypes(): void $queries = ['invalid']; /** @phpstan-ignore-next-line intentionally passing invalid query type */ - $db->getQueryCacheField(null, $queries); + $this->getQueryCacheField($db, null, $queries); } diff --git a/tests/unit/QueryCacheTest.php b/tests/unit/QueryCacheTest.php index 8103a0dd2..1b4ebade8 100644 --- a/tests/unit/QueryCacheTest.php +++ b/tests/unit/QueryCacheTest.php @@ -29,6 +29,35 @@ private function createDatabase(Adapter $cache, array $filters = [], ?DatabaseMe return $database; } + private function purgeCachedQueries(Database $database, string $collection, ?string $namespace = null): bool + { + $method = new \ReflectionMethod(Database::class, 'purgeCachedQueries'); + + return $method->invoke($database, $collection, $namespace); + } + + private function getQueryCacheKey(Database $database, string $collectionId, ?string $namespace = null): string + { + $method = new \ReflectionMethod(Database::class, 'getQueryCacheKey'); + + return $method->invoke($database, $collectionId, $namespace); + } + + /** + * @param array $queries + */ + private function getQueryCacheField( + Database $database, + ?Document $collection = null, + array $queries = [], + string $field = 'documents', + string $forPermission = Database::PERMISSION_READ, + ): ?string { + $method = new \ReflectionMethod(Database::class, 'getQueryCacheField'); + + return $method->invoke($database, $collection, $queries, $field, $forPermission); + } + /** * @param array $queries * @return array @@ -46,8 +75,8 @@ private function findWithCache( } $collectionDocument = $database->getCollection($collection); - $cacheKey = $database->getQueryCacheKey($collectionDocument->getId(), $namespace); - $cacheHash = $database->getQueryCacheField($collectionDocument, $queries); + $cacheKey = $this->getQueryCacheKey($database, $collectionDocument->getId(), $namespace); + $cacheHash = $this->getQueryCacheField($database, $collectionDocument, $queries); return $database->withCache( key: $cacheKey, @@ -326,8 +355,8 @@ public function testWithCacheCachesSingleDocument(): void $callbackCalls = 0; $collection = $database->getCollection('wafRules'); - $key = $database->getQueryCacheKey($collection->getId(), '_39'); - $hash = $database->getQueryCacheField($collection, field: 'document'); + $key = $this->getQueryCacheKey($database, $collection->getId(), '_39'); + $hash = $this->getQueryCacheField($database, $collection, field: 'document'); $first = $database->withCache( key: $key, @@ -361,8 +390,8 @@ public function testWithCacheCachesStaticQueryValues(): void $callbackCalls = 0; $collection = $database->getCollection('wafRules'); - $key = $database->getQueryCacheKey($collection->getId(), '_39'); - $hash = $database->getQueryCacheField($collection, field: 'count'); + $key = $this->getQueryCacheKey($database, $collection->getId(), '_39'); + $hash = $this->getQueryCacheField($database, $collection, field: 'count'); $first = $database->withCache( key: $key, @@ -429,7 +458,7 @@ public function testQueryCacheUsesCacheUntilPurged(): void $this->assertCount(1, $cached); $this->assertSame('rule-a', $cached[0]->getId()); - $this->assertTrue($database->purgeCachedQueries('wafRules', '_39')); + $this->assertTrue($this->purgeCachedQueries($database, 'wafRules', '_39')); $fresh = $this->findWithCache($database, 'wafRules', $queries, '_39'); $this->assertCount(2, $fresh); @@ -659,7 +688,7 @@ public function testQueryCacheReliesOnPurgeForDocumentSecurityCollections(): voi $this->assertCount(1, $cached); - $this->assertTrue($database->purgeCachedQueries('secureRules', '_39')); + $this->assertTrue($this->purgeCachedQueries($database, 'secureRules', '_39')); $fresh = $this->findWithCache($database, 'secureRules', $queries, '_39'); $this->assertSame([], $fresh); @@ -678,8 +707,8 @@ public function testQueryCacheFiltersDocumentSecurityPayloadsOnHit(): void $database->getAuthorization()->addRole(Role::user('user-1')->toString()); $collection = $database->getCollection('secureRules'); - $key = $database->getQueryCacheKey($collection->getId(), '_39'); - $hash = $database->getQueryCacheField($collection); + $key = $this->getQueryCacheKey($database, $collection->getId(), '_39'); + $hash = $this->getQueryCacheField($database, $collection); $database->getCache()->save($key, [ 'collection' => $collection->getId(), 'type' => 'documents', @@ -725,7 +754,7 @@ public function testQueryCacheRehydratesNestedDocumentPayloads(): void $collection = $database->getCollection('parents'); $cache->save( - $database->getQueryCacheKey($collection->getId(), '_39'), + $this->getQueryCacheKey($database, $collection->getId(), '_39'), [ 'collection' => $collection->getId(), 'type' => 'documents', @@ -740,7 +769,7 @@ public function testQueryCacheRehydratesNestedDocumentPayloads(): void ], ], ], - $database->getQueryCacheField($collection, $queries), + $this->getQueryCacheField($database, $collection, $queries), ); $parents = $this->findWithCache($database, 'parents', $queries, '_39'); @@ -782,13 +811,13 @@ public function testQueryCacheRefreshesInvalidPayload(): void $collection = $database->getCollection('wafRules'); $cache->save( - $database->getQueryCacheKey($collection->getId(), '_39'), + $this->getQueryCacheKey($database, $collection->getId(), '_39'), [ 'collection' => $collection->getId(), 'type' => 'documents', 'value' => 'invalid', ], - $database->getQueryCacheField($collection, $queries), + $this->getQueryCacheField($database, $collection, $queries), ); $rules = $this->findWithCache($database, 'wafRules', $queries, '_39'); @@ -833,7 +862,7 @@ public function testQueryCacheRefreshesInvalidPayloadEntry(): void $collection = $database->getCollection('wafRules'); $cache->save( - $database->getQueryCacheKey($collection->getId(), '_39'), + $this->getQueryCacheKey($database, $collection->getId(), '_39'), [ 'collection' => $collection->getId(), 'type' => 'documents', @@ -845,7 +874,7 @@ public function testQueryCacheRefreshesInvalidPayloadEntry(): void 'invalid', ], ], - $database->getQueryCacheField($collection, $queries), + $this->getQueryCacheField($database, $collection, $queries), ); $rules = $this->findWithCache($database, 'wafRules', $queries, '_39'); diff --git a/tests/unit/WithCacheLeaseTest.php b/tests/unit/WithCacheLeaseTest.php index 8eafd3540..b363dde84 100644 --- a/tests/unit/WithCacheLeaseTest.php +++ b/tests/unit/WithCacheLeaseTest.php @@ -39,7 +39,14 @@ protected function setUp(): void 'name' => 'fresh', ])); - $this->key = $this->database->getQueryCacheKey('projects'); + $this->key = $this->getQueryCacheKey($this->database, 'projects'); + } + + private function getQueryCacheKey(Database $database, string $collectionId, ?string $namespace = null): string + { + $method = new \ReflectionMethod(Database::class, 'getQueryCacheKey'); + + return $method->invoke($database, $collectionId, $namespace); } public function testStaleListWriteAfterConcurrentPurgeIsRejected(): void From 3c789ae6b3e70225a83df9cb4065d4f5f8ecd159 Mon Sep 17 00:00:00 2001 From: Prem Palanisamy Date: Tue, 7 Jul 2026 13:00:30 +0100 Subject: [PATCH 2/2] Address query cache helper review --- src/Database/Database.php | 2 ++ tests/unit/CacheKeyTest.php | 24 ++--------------- tests/unit/QueryCacheTest.php | 31 ++-------------------- tests/unit/QueryCacheTestHelpers.php | 39 ++++++++++++++++++++++++++++ tests/unit/WithCacheLeaseTest.php | 9 ++----- 5 files changed, 47 insertions(+), 58 deletions(-) create mode 100644 tests/unit/QueryCacheTestHelpers.php diff --git a/src/Database/Database.php b/src/Database/Database.php index 44d712f8b..4fbac8d3a 100644 --- a/src/Database/Database.php +++ b/src/Database/Database.php @@ -8584,6 +8584,7 @@ public function find(string $collection, array $queries = [], string $forPermiss * @param string|null $namespace * @return bool */ + /** @phpstan-ignore-next-line exercised through query cache tests */ private function purgeCachedQueries(string $collection, ?string $namespace = null): bool { $collectionDocument = $this->silent(fn () => $this->getCollection($collection)); @@ -9744,6 +9745,7 @@ private function getQueryCacheKey(string $collectionId, ?string $namespace = nul * @param string $forPermission * @return string|null */ + /** @phpstan-ignore-next-line exercised through query cache tests */ private function getQueryCacheField( ?Document $collection = null, array $queries = [], diff --git a/tests/unit/CacheKeyTest.php b/tests/unit/CacheKeyTest.php index be9eac583..2958f3a16 100644 --- a/tests/unit/CacheKeyTest.php +++ b/tests/unit/CacheKeyTest.php @@ -13,6 +13,8 @@ class CacheKeyTest extends TestCase { + use QueryCacheTestHelpers; + /** * @param array $instanceFilters */ @@ -33,28 +35,6 @@ private function getHashKey(Database $db, string $collection = 'col', string $do return $hashKey; } - private function getQueryCacheKey(Database $db, string $collectionId, ?string $namespace = null): string - { - $method = new \ReflectionMethod(Database::class, 'getQueryCacheKey'); - - return $method->invoke($db, $collectionId, $namespace); - } - - /** - * @param array $queries - */ - private function getQueryCacheField( - Database $db, - ?Document $collection = null, - array $queries = [], - string $field = 'documents', - string $forPermission = Database::PERMISSION_READ, - ): ?string { - $method = new \ReflectionMethod(Database::class, 'getQueryCacheField'); - - return $method->invoke($db, $collection, $queries, $field, $forPermission); - } - public function testSameConfigProducesSameCacheKey(): void { $db1 = $this->createDatabase(); diff --git a/tests/unit/QueryCacheTest.php b/tests/unit/QueryCacheTest.php index 1b4ebade8..573c4083a 100644 --- a/tests/unit/QueryCacheTest.php +++ b/tests/unit/QueryCacheTest.php @@ -14,6 +14,8 @@ class QueryCacheTest extends TestCase { + use QueryCacheTestHelpers; + /** * @param array $filters */ @@ -29,35 +31,6 @@ private function createDatabase(Adapter $cache, array $filters = [], ?DatabaseMe return $database; } - private function purgeCachedQueries(Database $database, string $collection, ?string $namespace = null): bool - { - $method = new \ReflectionMethod(Database::class, 'purgeCachedQueries'); - - return $method->invoke($database, $collection, $namespace); - } - - private function getQueryCacheKey(Database $database, string $collectionId, ?string $namespace = null): string - { - $method = new \ReflectionMethod(Database::class, 'getQueryCacheKey'); - - return $method->invoke($database, $collectionId, $namespace); - } - - /** - * @param array $queries - */ - private function getQueryCacheField( - Database $database, - ?Document $collection = null, - array $queries = [], - string $field = 'documents', - string $forPermission = Database::PERMISSION_READ, - ): ?string { - $method = new \ReflectionMethod(Database::class, 'getQueryCacheField'); - - return $method->invoke($database, $collection, $queries, $field, $forPermission); - } - /** * @param array $queries * @return array diff --git a/tests/unit/QueryCacheTestHelpers.php b/tests/unit/QueryCacheTestHelpers.php new file mode 100644 index 000000000..78ef999fd --- /dev/null +++ b/tests/unit/QueryCacheTestHelpers.php @@ -0,0 +1,39 @@ +invoke($database, $collection, $namespace); + } + + private function getQueryCacheKey(Database $database, string $collectionId, ?string $namespace = null): string + { + $method = new \ReflectionMethod(Database::class, 'getQueryCacheKey'); + + return $method->invoke($database, $collectionId, $namespace); + } + + /** + * @param array $queries + */ + private function getQueryCacheField( + Database $database, + ?Document $collection = null, + array $queries = [], + string $field = 'documents', + string $forPermission = Database::PERMISSION_READ, + ): ?string { + $method = new \ReflectionMethod(Database::class, 'getQueryCacheField'); + + return $method->invoke($database, $collection, $queries, $field, $forPermission); + } +} diff --git a/tests/unit/WithCacheLeaseTest.php b/tests/unit/WithCacheLeaseTest.php index b363dde84..e97c2d7bd 100644 --- a/tests/unit/WithCacheLeaseTest.php +++ b/tests/unit/WithCacheLeaseTest.php @@ -14,6 +14,8 @@ class WithCacheLeaseTest extends TestCase { + use QueryCacheTestHelpers; + private LeasableMemoryCache $cacheAdapter; private Database $database; @@ -42,13 +44,6 @@ protected function setUp(): void $this->key = $this->getQueryCacheKey($this->database, 'projects'); } - private function getQueryCacheKey(Database $database, string $collectionId, ?string $namespace = null): string - { - $method = new \ReflectionMethod(Database::class, 'getQueryCacheKey'); - - return $method->invoke($database, $collectionId, $namespace); - } - public function testStaleListWriteAfterConcurrentPurgeIsRejected(): void { $hash = 'list-hash';