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
55 changes: 55 additions & 0 deletions src/Database/Database.php
Original file line number Diff line number Diff line change
Expand Up @@ -443,6 +443,11 @@ class Database
*/
protected array $documentTypes = [];

/**
* Document transformer callback
* @var callable|null
*/
protected $transformer = null;

/**
* @var Authorization
Expand Down Expand Up @@ -1134,6 +1139,47 @@ public function getInstanceFilters(): array
return $this->instanceFilters;
}

/**
* Set document transformer
*
* Called after decode for every document retrieved, including nested relationships.
* Transformer receives custom document types (if mapped via setDocumentType) and
* should return the same type.
*
* @param callable|null $transformer fn(Document $document, Document $collection, Database $db): Document
* @return static
*/
public function setTransformer(?callable $transformer): static
{
$this->transformer = $transformer;
return $this;
}

/**
* Get document transformer
*
* @return callable|null
*/
public function getTransformer(): ?callable
{
return $this->transformer;
}

/**
* Transform a document using the configured transformer
*
* @param Document $document
* @param Document $collection
* @return Document
*/
protected function transform(Document $document, Document $collection): Document
{
if ($this->transformer === null || $document->isEmpty()) {
return $document;
}
return ($this->transformer)($document, $collection, $this);
}

/**
* Enable validation
*
Expand Down Expand Up @@ -4309,6 +4355,8 @@ public function getDocument(string $collection, string $id, array $queries = [],
$document = $documents[0];
}

$document = $this->transform($document, $collection);

$relationships = \array_filter(
$collection->getAttribute('attributes', []),
fn ($attribute) => $attribute['type'] === Database::VAR_RELATIONSHIP
Expand Down Expand Up @@ -5036,6 +5084,8 @@ public function createDocument(string $collection, Document $document): Document
$document = $this->createDocumentInstance($collection->getId(), $document->getArrayCopy());
}

$document = $this->transform($document, $collection);

$this->trigger(self::EVENT_DOCUMENT_CREATE, $document);

return $document;
Expand Down Expand Up @@ -5734,6 +5784,8 @@ public function updateDocument(string $collection, string $id, Document $documen
$document = $this->createDocumentInstance($collection->getId(), $document->getArrayCopy());
}

$document = $this->transform($document, $collection);

Comment on lines +5787 to +5788
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Refetch path can apply the transformer twice.

When operators trigger refetchDocuments(), it uses find() which now transforms results. These call sites then transform again. If transformers aren’t idempotent, this produces incorrect data. Consider a findRaw/skipTransform path for refetchDocuments() or guard against a second transform after refetch.

Also applies to: 6829-6830

🤖 Prompt for AI Agents
In `@src/Database/Database.php` around lines 5787 - 5788, refetchDocuments() is
ending up transforming documents twice because find() now applies transform()
and callers later call transform() again; update the refetch path to use a
non-transforming read (add/use a findRaw/find(..., ['skipTransform' => true])
variant) or change refetchDocuments() to call the new findRaw helper, and
alternatively add a guard in transform() (e.g., a metadata flag on the document
like __transformed) to no-op when already transformed; locate transform(),
find(), and refetchDocuments() in the diff and implement the
findRaw/skipTransform option and/or the idempotence guard so refetch does not
double-transform documents.

$this->trigger(self::EVENT_DOCUMENT_UPDATE, $document);

return $document;
Expand Down Expand Up @@ -6774,6 +6826,8 @@ public function upsertDocumentsWithIncrease(
$doc = $this->decode($collection, $doc);
}

$doc = $this->transform($doc, $collection);

if ($this->getSharedTables() && $this->getTenantPerDocument()) {
$this->withTenant($doc->getTenant(), function () use ($collection, $doc) {
$this->purgeCachedDocument($collection->getId(), $doc->getId());
Expand Down Expand Up @@ -7843,6 +7897,7 @@ public function find(string $collection, array $queries = [], string $forPermiss
$node->setAttribute('$collection', $collection->getId());
}

$node = $this->transform($node, $collection);
$results[$index] = $node;
}

Expand Down
2 changes: 2 additions & 0 deletions tests/e2e/Adapter/Base.php
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
use Tests\E2E\Adapter\Scopes\RelationshipTests;
use Tests\E2E\Adapter\Scopes\SchemalessTests;
use Tests\E2E\Adapter\Scopes\SpatialTests;
use Tests\E2E\Adapter\Scopes\TransformerTests;
use Tests\E2E\Adapter\Scopes\VectorTests;
use Utopia\Database\Database;
use Utopia\Database\Validator\Authorization;
Expand All @@ -34,6 +35,7 @@ abstract class Base extends TestCase
use SpatialTests;
use SchemalessTests;
use ObjectAttributeTests;
use TransformerTests;
use VectorTests;
use GeneralTests;

Expand Down
Loading
Loading