From abcea3a5fd0d76bc8c0d337bc40bd6c843fce663 Mon Sep 17 00:00:00 2001 From: fogelito Date: Mon, 18 May 2026 12:33:43 +0300 Subject: [PATCH 1/6] Add trigger getDocument --- src/Database/Adapter/SQL.php | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/Database/Adapter/SQL.php b/src/Database/Adapter/SQL.php index 4d640c900..e56678f8e 100644 --- a/src/Database/Adapter/SQL.php +++ b/src/Database/Adapter/SQL.php @@ -394,6 +394,8 @@ public function getDocument(Document $collection, string $id, array $queries = [ $sql .= " {$forUpdate}"; } + $sql = $this->trigger(Database::EVENT_DOCUMENT_READ, $sql); + $stmt = $this->getPDO()->prepare($sql); $stmt->bindValue(':_uid', $id); From 4c291ddc7b7fd345507c8d5e3933041ab71ccd4a Mon Sep 17 00:00:00 2001 From: "claude[bot]" <41898282+claude[bot]@users.noreply.github.com> Date: Mon, 18 May 2026 09:58:07 +0000 Subject: [PATCH 2/6] =?UTF-8?q?(fix):=20CI=20=E2=80=94=20unregister=20test?= =?UTF-8?q?=20transformation=20to=20avoid=20polluting=20later=20tests?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit testTransformations registers a before() callback that rewrites SQL to "SELECT 1". Now that SQL.php::getDocument triggers EVENT_DOCUMENT_READ, this callback persists across subsequent tests in the same class instance, causing PDO HY093 errors when bindValue(':_uid') is called on the parameter-less rewritten query. Wrap the assertion in try/finally and unregister via the adapter's before(name, null) form. Co-Authored-By: Claude Opus 4.7 --- tests/e2e/Adapter/Scopes/CollectionTests.php | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/tests/e2e/Adapter/Scopes/CollectionTests.php b/tests/e2e/Adapter/Scopes/CollectionTests.php index d5d23e129..19b779ab7 100644 --- a/tests/e2e/Adapter/Scopes/CollectionTests.php +++ b/tests/e2e/Adapter/Scopes/CollectionTests.php @@ -1674,9 +1674,13 @@ public function testTransformations(): void return "SELECT 1"; }); - $result = $database->getDocument('docs', 'doc1'); + try { + $result = $database->getDocument('docs', 'doc1'); - $this->assertTrue($result->isEmpty()); + $this->assertTrue($result->isEmpty()); + } finally { + $database->getAdapter()->before(Database::EVENT_DOCUMENT_READ, 'test', null); + } } public function testSetGlobalCollection(): void From 34c34820b2cd3d371eee22cead7488964e7229b4 Mon Sep 17 00:00:00 2001 From: "claude[bot]" <41898282+claude[bot]@users.noreply.github.com> Date: Mon, 18 May 2026 10:15:29 +0000 Subject: [PATCH 3/6] =?UTF-8?q?(fix):=20CI=20=E2=80=94=20testTransformatio?= =?UTF-8?q?ns:=20preserve=20placeholders=20in=20transformed=20SQL?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The transformation rewrote the prepared SQL to "SELECT 1", but SQL.php's getDocument still calls bindValue(':_uid', $id) (and ':_tenant' for sharedTables). PDO throws HY093 because the rewritten query has no placeholders. Wrap the original query as a derived table with WHERE 1=0 instead. The inner query keeps the :_uid / :_tenant placeholders so the bindValue calls match, while the outer WHERE forces zero rows so isEmpty() still asserts the trigger was applied. Co-Authored-By: Claude Opus 4.7 --- tests/e2e/Adapter/Scopes/CollectionTests.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/e2e/Adapter/Scopes/CollectionTests.php b/tests/e2e/Adapter/Scopes/CollectionTests.php index 19b779ab7..84311661b 100644 --- a/tests/e2e/Adapter/Scopes/CollectionTests.php +++ b/tests/e2e/Adapter/Scopes/CollectionTests.php @@ -1671,7 +1671,7 @@ public function testTransformations(): void ])); $database->before(Database::EVENT_DOCUMENT_READ, 'test', function (string $query) { - return "SELECT 1"; + return "SELECT * FROM ({$query}) AS sub_q WHERE 1 = 0"; }); try { From 6ce88f4356e147fbb2b7ab3e7ff4b2c672c59f00 Mon Sep 17 00:00:00 2001 From: fogelito Date: Mon, 18 May 2026 14:04:38 +0300 Subject: [PATCH 4/6] update tests --- src/Database/Database.php | 4 ++-- tests/e2e/Adapter/Scopes/CollectionTests.php | 21 ++++++++++++++++++-- 2 files changed, 21 insertions(+), 4 deletions(-) diff --git a/src/Database/Database.php b/src/Database/Database.php index 0e8c7a74f..f9fad6808 100644 --- a/src/Database/Database.php +++ b/src/Database/Database.php @@ -732,10 +732,10 @@ public function on(string $event, string $name, ?callable $callback): static * * @param string $event * @param string $name - * @param callable $callback + * @param ?callable $callback * @return $this */ - public function before(string $event, string $name, callable $callback): static + public function before(string $event, string $name, ?callable $callback): static { $this->adapter->before($event, $name, $callback); diff --git a/tests/e2e/Adapter/Scopes/CollectionTests.php b/tests/e2e/Adapter/Scopes/CollectionTests.php index d5d23e129..732cc7a06 100644 --- a/tests/e2e/Adapter/Scopes/CollectionTests.php +++ b/tests/e2e/Adapter/Scopes/CollectionTests.php @@ -3,6 +3,7 @@ namespace Tests\E2E\Adapter\Scopes; use Exception; +use Utopia\Database\Adapter\SQL; use Utopia\Database\Database; use Utopia\Database\Document; use Utopia\Database\Exception as DatabaseException; @@ -1670,13 +1671,29 @@ public function testTransformations(): void 'name' => 'value1', ])); - $database->before(Database::EVENT_DOCUMENT_READ, 'test', function (string $query) { - return "SELECT 1"; + $originalMetadata = $database->getMetadata(); + + $database->setMetadata('scope', 'api.users'); + + $capturedSql = ''; + $database->before(Database::EVENT_DOCUMENT_READ, 'test', function (string $sql) use (&$capturedSql) { + $sql .= ' AND 1=0'; + $capturedSql = $sql; + return $sql; }); $result = $database->getDocument('docs', 'doc1'); $this->assertTrue($result->isEmpty()); + if (!$database->getAdapter() instanceof SQL) { + $this->assertStringContainsString('/* scope: api.users */', $capturedSql); + } + + $database->before(Database::EVENT_DOCUMENT_READ, 'test', null); + $database->resetMetadata(); + foreach ($originalMetadata as $key => $value) { + $database->setMetadata($key, $value); + } } public function testSetGlobalCollection(): void From 82a602add339c6e9ff590756d08532b932b556d9 Mon Sep 17 00:00:00 2001 From: fogelito Date: Mon, 18 May 2026 14:09:35 +0300 Subject: [PATCH 5/6] update tests --- tests/e2e/Adapter/Scopes/CollectionTests.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tests/e2e/Adapter/Scopes/CollectionTests.php b/tests/e2e/Adapter/Scopes/CollectionTests.php index 732cc7a06..aaac84a98 100644 --- a/tests/e2e/Adapter/Scopes/CollectionTests.php +++ b/tests/e2e/Adapter/Scopes/CollectionTests.php @@ -1685,7 +1685,8 @@ public function testTransformations(): void $result = $database->getDocument('docs', 'doc1'); $this->assertTrue($result->isEmpty()); - if (!$database->getAdapter() instanceof SQL) { + + if ($database->getAdapter() instanceof SQL) { $this->assertStringContainsString('/* scope: api.users */', $capturedSql); } From 0d95a66df04a649b16c151c826d3f78a3707c4f5 Mon Sep 17 00:00:00 2001 From: fogelito Date: Mon, 18 May 2026 15:52:38 +0300 Subject: [PATCH 6/6] resetMetadata --- tests/e2e/Adapter/Scopes/CollectionTests.php | 5 ----- 1 file changed, 5 deletions(-) diff --git a/tests/e2e/Adapter/Scopes/CollectionTests.php b/tests/e2e/Adapter/Scopes/CollectionTests.php index aaac84a98..1cbebd1db 100644 --- a/tests/e2e/Adapter/Scopes/CollectionTests.php +++ b/tests/e2e/Adapter/Scopes/CollectionTests.php @@ -1671,8 +1671,6 @@ public function testTransformations(): void 'name' => 'value1', ])); - $originalMetadata = $database->getMetadata(); - $database->setMetadata('scope', 'api.users'); $capturedSql = ''; @@ -1692,9 +1690,6 @@ public function testTransformations(): void $database->before(Database::EVENT_DOCUMENT_READ, 'test', null); $database->resetMetadata(); - foreach ($originalMetadata as $key => $value) { - $database->setMetadata($key, $value); - } } public function testSetGlobalCollection(): void