Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
244 changes: 116 additions & 128 deletions composer.lock

Large diffs are not rendered by default.

5 changes: 5 additions & 0 deletions src/Database/Adapter.php
Original file line number Diff line number Diff line change
Expand Up @@ -1122,6 +1122,11 @@ abstract public function getSupportForVectors(): bool;
*/
abstract public function getSupportForCacheSkipOnFailure(): bool;

/**
* @return bool
*/
abstract public function getSupportForCaching(): bool;
Comment thread
coderabbitai[bot] marked this conversation as resolved.

/**
* Is reconnection supported?
*
Expand Down
5 changes: 5 additions & 0 deletions src/Database/Adapter/Memory.php
Original file line number Diff line number Diff line change
Expand Up @@ -1940,6 +1940,11 @@ public function getSupportForCacheSkipOnFailure(): bool
return false;
}

public function getSupportForCaching(): bool
{
return true;
}

public function getSupportForReconnection(): bool
{
return false;
Expand Down
5 changes: 5 additions & 0 deletions src/Database/Adapter/Mongo.php
Original file line number Diff line number Diff line change
Expand Up @@ -3682,6 +3682,11 @@ public function getSupportForCacheSkipOnFailure(): bool
return false;
}

public function getSupportForCaching(): bool
{
return true;
}

/**
* Is hostname supported?
*
Expand Down
5 changes: 5 additions & 0 deletions src/Database/Adapter/Pool.php
Original file line number Diff line number Diff line change
Expand Up @@ -518,6 +518,11 @@ public function getSupportForCacheSkipOnFailure(): bool
return $this->delegate(__FUNCTION__, \func_get_args());
}

public function getSupportForCaching(): bool
{
return $this->delegate(__FUNCTION__, \func_get_args());
}

public function getSupportForReconnection(): bool
{
return $this->delegate(__FUNCTION__, \func_get_args());
Expand Down
7 changes: 7 additions & 0 deletions src/Database/Adapter/Redis.php
Original file line number Diff line number Diff line change
Expand Up @@ -870,6 +870,13 @@ public function getSupportForCacheSkipOnFailure(): bool
return false;
}

public function getSupportForCaching(): bool
{
// The Redis adapter is itself the store; reads hit Redis directly and
// it runs with a no-op cache, so the Database cache layer is bypassed.
return false;
}

public function getSupportForReconnection(): bool
{
return false;
Expand Down
5 changes: 5 additions & 0 deletions src/Database/Adapter/SQL.php
Original file line number Diff line number Diff line change
Expand Up @@ -1061,6 +1061,11 @@ public function getSupportForCacheSkipOnFailure(): bool
return true;
}

public function getSupportForCaching(): bool
{
return true;
}

/**
* Is hostname supported?
*
Expand Down
64 changes: 50 additions & 14 deletions src/Database/Database.php
Original file line number Diff line number Diff line change
Expand Up @@ -179,6 +179,9 @@ class Database
// Cache
public const TTL = 60 * 60 * 24; // 24 hours

// Cache "Not Found" results
private const CACHE_EMPTY_MARKER = '$empty';

// Events
public const EVENT_ALL = '*';

Expand Down Expand Up @@ -4875,6 +4878,11 @@ public function getDocument(string $collection, string $id, array $queries = [],
}
}

// Negative cache hit
if (\is_array($cached) && isset($cached[self::CACHE_EMPTY_MARKER])) {
return $this->createDocumentInstance($collection->getId(), []);
}

if ($cached) {
$document = $this->createDocumentInstance($collection->getId(), $cached);

Expand Down Expand Up @@ -4921,6 +4929,18 @@ public function getDocument(string $collection, string $id, array $queries = [],
);

if ($document->isEmpty()) {
if (!$forUpdate && empty($relationships)) {
try {
$marker = [self::CACHE_EMPTY_MARKER => true];

if ($this->cache->saveWithLease($documentKey, $marker, $hashKey, $generation) !== false) {
$this->cache->save($collectionKey, 'empty', $documentKey);
}
} catch (Exception $e) {
Console::warning('Failed to save empty document to cache: ' . $e->getMessage());
}
}

return $this->createDocumentInstance($collection->getId(), []);
}

Expand Down Expand Up @@ -5700,6 +5720,10 @@ public function createDocument(string $collection, Document $document): Document
return $this->adapter->createDocument($collection, $document);
});

// Clear any negative-cache entry for this id: a prior read may have
// recorded it as missing before this insert committed.
$this->withDocumentTenant($document, fn () => $this->purgeCachedDocumentInternal($collection->getId(), $document->getId()));

if (!$this->inBatchRelationshipPopulation && $this->resolveRelationships) {
// Use the write stack depth for proper MAX_DEPTH enforcement during creation
$fetchDepth = count($this->relationshipWriteStack);
Expand Down Expand Up @@ -5826,6 +5850,9 @@ public function createDocuments(
$document = $this->casting($collection, $document);
$document = $this->decode($collection, $document);

// Clear any negative-cache entry recorded before this insert.
$this->withDocumentTenant($document, fn () => $this->purgeCachedDocumentInternal($collection->getId(), $document->getId()));

try {
$onNext && $onNext($document);
} catch (\Throwable $e) {
Expand Down Expand Up @@ -7528,13 +7555,7 @@ public function upsertDocumentsWithIncrease(
$doc = $this->decode($collection, $doc);
}

if ($this->getSharedTables() && $this->getTenantPerDocument()) {
$this->withTenant($doc->getTenant(), function () use ($collection, $doc) {
$this->purgeCachedDocument($collection->getId(), $doc->getId());
});
} else {
$this->purgeCachedDocument($collection->getId(), $doc->getId());
}
$this->withDocumentTenant($doc, fn () => $this->purgeCachedDocument($collection->getId(), $doc->getId()));

$old = $chunk[$index]->getOld();

Expand Down Expand Up @@ -8350,13 +8371,7 @@ public function deleteDocuments(
});

foreach ($batch as $index => $document) {
if ($this->getSharedTables() && $this->getTenantPerDocument()) {
$this->withTenant($document->getTenant(), function () use ($collection, $document) {
$this->purgeCachedDocument($collection->getId(), $document->getId());
});
} else {
$this->purgeCachedDocument($collection->getId(), $document->getId());
}
$this->withDocumentTenant($document, fn () => $this->purgeCachedDocument($collection->getId(), $document->getId()));
try {
$onNext && $onNext($document, $old[$index]);
} catch (Throwable $th) {
Expand Down Expand Up @@ -8427,6 +8442,27 @@ protected function purgeCachedDocumentInternal(string $collectionId, ?string $id
return true;
}

/**
* Run a per-document cache operation under the document's own tenant.
*
* With tenant-per-document, cache keys are scoped by the adapter's current
* tenant, so a document's purge must run under that document's tenant to
* target the right key; otherwise the callback runs as-is.
*
* @param Document $document
* @param callable():mixed $callback
* @return void
* @throws Exception
*/
private function withDocumentTenant(Document $document, callable $callback): void
{
if ($this->getSharedTables() && $this->getTenantPerDocument()) {
$this->withTenant($document->getTenant(), $callback);
} else {
$callback();
}
}

Comment thread
coderabbitai[bot] marked this conversation as resolved.
/**
* Cleans a specific document from cache and triggers EVENT_DOCUMENT_PURGE.
* And related document reference in the collection cache.
Expand Down
Loading
Loading