Skip to content

Conversation

@abnegate
Copy link
Member

@abnegate abnegate commented Sep 30, 2025

Summary by CodeRabbit

  • New Features

    • MongoDB support: native adapter, PHP mongodb extension, composer requirements, and local mongo + mongo-express services.
  • Refactor

    • Default ID format switched to UUIDv7.
    • Increased key/label/permission length limits to 255.
    • New adapter capability flags and casting lifecycle hooks.
  • Chores

    • Updated MySQL and Redis images; docker/dev config extended with volumes/secrets for Mongo.
  • Tests

    • Expanded adapter matrix (Mongo/SharedTables variants) and added capability-aware e2e/unit tests.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Oct 2, 2025

✅ Actions performed

Full review triggered.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 4

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (5)
tests/e2e/Adapter/Scopes/GeneralTests.php (1)

94-101: Restore authorization before early-return guards

Authorization::disable() runs before the capability guard. When the guard short-circuits, we return without ever calling Authorization::reset(), so the rest of the suite executes with authorization disabled. Move the guard before disabling authorization (and likewise in testPreserveDatesCreate) so we only disable when the test actually runs.

-        Authorization::disable();
-
         /** @var Database $database */
         $database = static::getDatabase();
         if (!$database->getAdapter()->getSupportForAttributes()) {
             $this->expectNotToPerformAssertions();
             return;
         }
+
+        Authorization::disable();

Apply the same pattern to testPreserveDatesCreate.

Also applies to: 193-200

tests/e2e/Adapter/Scopes/PermissionTests.php (1)

936-949: Avoid leaking Authorization state when relationships unsupported

Here we set the active role to Role::users() before checking the capability. If the adapter lacks relationship support, the guard returns immediately and never restores the previous authorization context, so subsequent tests keep running with Role::users() set. Place the guard before mutating authorization (and do the same in testCollectionPermissionsRelationshipsGetWorks).

-        Authorization::cleanRoles();
-        Authorization::setRole(Role::users()->toString());
-
         /** @var Database $database */
         $database = static::getDatabase();
 
         if (!$database->getAdapter()->getSupportForRelationships()) {
             $this->expectNotToPerformAssertions();
             return;
         }
+
+        Authorization::cleanRoles();
+        Authorization::setRole(Role::users()->toString());
src/Database/Database.php (3)

2047-2056: Bug: fall-through makes spatial defaults always throw.

Missing break; after spatial cases causes the default branch to throw “Unknown attribute type”.

             case self::VAR_POINT:
             case self::VAR_LINESTRING:
             case self::VAR_POLYGON:
                 // Spatial types expect arrays as default values
                 if ($defaultType !== 'array') {
                     throw new DatabaseException('Default value for spatial type ' . $type . ' must be an array');
                 }
-                // no break
+                break;
             default:
                 throw new DatabaseException('Unknown attribute type: ' . $type . '. Must be one of ' . self::VAR_STRING . ', ' . self::VAR_INTEGER . ', ' . self::VAR_FLOAT . ', ' . self::VAR_BOOLEAN . ', ' . self::VAR_DATETIME . ', ' . self::VAR_RELATIONSHIP . ', ' . self::VAR_POINT . ', ' . self::VAR_LINESTRING . ', ' . self::VAR_POLYGON);

4714-4812: $updates pre‑casting occurs after DB write; move it before.

$updates = $this->adapter->castingBefore(...); happens post updateDocuments(), so it has no effect.

         $updates = $this->encode($collection, $updates);
         // Check new document structure
         $validator = new PartialStructure(
             $collection,
             $this->adapter->getIdAttributeType(),
             $this->adapter->getMinDateTime(),
             $this->adapter->getMaxDateTime(),
             $this->adapter->getSupportForAttributes()
         );
@@
-            $this->withTransaction(function () use ($collection, $updates, &$batch, $currentPermissions) {
+            // Pre-cast updates for adapter before write
+            $updatesPre = $this->adapter->castingBefore($collection, $updates);
+            $this->withTransaction(function () use ($collection, $updatesPre, &$batch, $currentPermissions) {
                 foreach ($batch as $index => $document) {
@@
-                $this->adapter->updateDocuments(
+                $this->adapter->updateDocuments(
                     $collection,
-                    $updates,
+                    $updatesPre,
                     $batch
                 );
             });
@@
-            $updates = $this->adapter->castingBefore($collection, $updates);
+            // Already pre-cast above; no-op removed

4001-4043: createDocuments: result of castingBefore not persisted.

Iterates by value; if castingBefore returns a new instance, it’s lost. Iterate with index and assign back.

-        foreach ($documents as $document) {
+        foreach ($documents as $i => $document) {
             $createdAt = $document->getCreatedAt();
             $updatedAt = $document->getUpdatedAt();
@@
-            $document = $this->encode($collection, $document);
+            $document = $this->encode($collection, $document);
@@
-            $document = $this->adapter->castingBefore($collection, $document);
+            $document = $this->adapter->castingBefore($collection, $document);
+            $documents[$i] = $document;
         }
♻️ Duplicate comments (5)
src/Database/Validator/Index.php (1)

415-453: Fix order-sensitive comparison and add self-comparison skip.

The bidirectional array_diff comparisons on lines 424-428 (attributes) and 430-434 (orders) are order-insensitive, but index column order matters. For example, an index on ['name', 'age'] is functionally different from ['age', 'name'].

Additionally, when updating an existing index, this check may self-conflict (similar to the issue in checkMultipleFulltextIndex). Add a guard to skip comparison when $existingIndex->getId() === $index->getId().

Apply this diff to fix both issues:

         foreach ($this->indexes as $existingIndex) {
+            if ($existingIndex->getId() === $index->getId()) {
+                continue;
+            }
+
             $existingAttributes = $existingIndex->getAttribute('attributes', []);
             $existingOrders = $existingIndex->getAttribute('orders', []);
             $existingType = $existingIndex->getAttribute('type', '');
 
-            $attributesMatch = false;
-            if (empty(array_diff($existingAttributes, $indexAttributes)) &&
-                empty(array_diff($indexAttributes, $existingAttributes))) {
-                $attributesMatch = true;
-            }
+            $attributesMatch = ($existingAttributes === $indexAttributes);
 
-            $ordersMatch = false;
-            if (empty(array_diff($existingOrders, $indexOrders)) &&
-                empty(array_diff($indexOrders, $existingOrders))) {
-                $ordersMatch = true;
-            }
+            $ordersMatch = ($existingOrders === $indexOrders);
 
             if ($attributesMatch && $ordersMatch) {
src/Database/Validator/Query/Filter.php (1)

98-106: Still enforce spatial guard before early-return

We still let spatial methods through when the attribute isn’t in the schema and supportForAttributes is false, so invalid distance/geometry queries slip past every safety check. Please perform the spatial guard (and only return true afterwards) even when attributes aren’t supported.

-        if (!$this->supportForAttributes && !isset($this->schema[$attribute])) {
-            if (count($values) > $this->maxValuesCount) {
-                $this->message = 'Query on attribute has greater than ' . $this->maxValuesCount . ' values: ' . $attribute;
-                return false;
-            }
-
-            return true;
-        }
+        if (!isset($this->schema[$attribute])) {
+            if (count($values) > $this->maxValuesCount) {
+                $this->message = 'Query on attribute has greater than ' . $this->maxValuesCount . ' values: ' . $attribute;
+                return false;
+            }
+
+            $query = new Query($method);
+            if ($query->isSpatialQuery()) {
+                $this->message = 'Spatial query "' . $method . '" cannot be applied on unknown attribute: ' . $attribute;
+                return false;
+            }
+
+            if (!$this->supportForAttributes) {
+                return true;
+            }
+        }
docker-compose.yml (1)

108-110: Advertise the Mongo service hostname in rs.initiate

Registering the member as localhost:27017 causes every other container to connect to itself instead of the Mongo service, breaking transactions/change streams. Use the Compose service hostname instead.

-              rs.initiate({_id: 'rs0', members: [{_id: 0, host: 'localhost:27017'}]})
+              rs.initiate({_id: 'rs0', members: [{_id: 0, host: 'mongo:27017'}]})
tests/e2e/Adapter/SharedTables/MongoDBTest.php (1)

55-60: Generate a per-run namespace to avoid cross-test collisions.
We previously flagged this: using the static namespace 'my_shared_tables' lets concurrent/dirty runs stomp on each other. Randomize the suffix (e.g. 'my_shared_tables_' . uniqid()), so each test run remains isolated.

-            ->setNamespace(static::$namespace = 'my_shared_tables');
+            ->setNamespace(static::$namespace = 'my_shared_tables_' . uniqid());
tests/e2e/Adapter/Scopes/DocumentTests.php (1)

6149-6225: Drop attribute-only calls from the schemaless test.
This test runs only when getSupportForAttributes() is false, yet it still calls createAttribute()/increaseDocumentAttribute()/decreaseDocumentAttribute(). Those APIs are guaranteed to throw on adapters without attribute support, so the test will fail before exercising schemaless behavior. Remove or guard all attribute-specific code so the schemaless adapters can execute the flow.

-        $database->createAttribute($colName, 'key', Database::VAR_STRING, 50, true);
-        $database->createAttribute($colName, 'value', Database::VAR_STRING, 50, false, 'value');-        $database->createAttribute($colName, 'counter', Database::VAR_INTEGER, 0, false, 0);
-        $docS = $database->getDocument($colName, 'docS');
-        $this->assertEquals(0, $docS->getAttribute('counter'));
-        $docS = $database->increaseDocumentAttribute($colName, 'docS', 'counter', 5);
-        $this->assertEquals(5, $docS->getAttribute('counter'));
-        $docS = $database->decreaseDocumentAttribute($colName, 'docS', 'counter', 3);
-        $this->assertEquals(2, $docS->getAttribute('counter'));
-
-        $deletedByCounter = $database->deleteDocuments($colName, [Query::equal('counter', [2])]);
-        $this->assertEquals(1, $deletedByCounter);

(Keep the rest of the schemaless assertions intact.)

🧹 Nitpick comments (6)
composer.json (2)

51-51: Reconsider wildcard version for laravel/pint in require-dev.

Changing laravel/pint from a fixed version to "*" removes version constraints, which can lead to:

  • Unpredictable CI/local environment behavior if breaking changes are introduced in new versions.
  • Difficulty reproducing builds and debugging formatting issues.

Apply this diff to use a permissive but bounded version range:

-        "laravel/pint": "*",
+        "laravel/pint": "^1.0",

57-59: Clarify the distinction between ext-pdo and mongodb/mongodb in suggests.

The suggests section now includes both ext-pdo and mongodb/mongodb. However:

  • ext-mongodb is already required on line 38, making the mongodb/mongodb suggestion potentially redundant or confusing.
  • The description for ext-pdo mentions MongoDB, which may mislead users.

Consider clarifying the distinction:

-        "ext-pdo": "Needed to support MariaDB, MySQL or SQLite Database Adapter",
-        "mongodb/mongodb": "Needed to support MongoDB Database Adapter"
+        "ext-pdo": "Needed to support MariaDB, MySQL, or SQLite Database Adapters",
+        "mongodb/mongodb": "Provides additional MongoDB utilities (ext-mongodb is already required)"
tests/e2e/Adapter/MongoDBTest.php (3)

38-42: Avoid flushAll(); prefer flushDB() or a scoped cache name.

flushAll() wipes all Redis DBs and can race with other tests. Use flushDB() or set a unique cache name/namespace and purge that.

-        $redis->flushAll();
+        // Limit impact to current Redis DB
+        $redis->flushDB();

79-97: Mark placeholders as incomplete instead of asserting true.

Use PHPUnit’s markTestIncomplete() to signal pending work and avoid false greens.

-    public function testRenameAttribute(): void
-    {
-        $this->assertTrue(true);
-    }
+    public function testRenameAttribute(): void
+    {
+        $this->markTestIncomplete('MongoDB: implement rename attribute test');
+    }

-    public function testRenameAttributeExisting(): void
-    {
-        $this->assertTrue(true);
-    }
+    public function testRenameAttributeExisting(): void
+    {
+        $this->markTestIncomplete('MongoDB: implement rename attribute (existing) test');
+    }

-    public function testUpdateAttributeStructure(): void
-    {
-        $this->assertTrue(true);
-    }
+    public function testUpdateAttributeStructure(): void
+    {
+        $this->markTestIncomplete('MongoDB: implement update attribute structure test');
+    }

-    public function testKeywords(): void
-    {
-        $this->assertTrue(true);
-    }
+    public function testKeywords(): void
+    {
+        $this->markTestIncomplete('MongoDB: implement keywords test');
+    }

99-107: Silence PHPMD unused-parameter warnings.

Either prefix with underscores or suppress via annotations.

-    protected static function deleteColumn(string $collection, string $column): bool
+    protected static function deleteColumn(string $_collection, string $_column): bool
     {
         return true;
     }

-    protected static function deleteIndex(string $collection, string $index): bool
+    protected static function deleteIndex(string $_collection, string $_index): bool
     {
         return true;
     }
src/Database/Database.php (1)

2347-2357: Minor: method name casing.

setattribute('key', ...) is inconsistent with setAttribute. PHP is case-insensitive here, but consistency helps.

-                ->setattribute('key', $newKey ?? $id)
+                ->setAttribute('key', $newKey ?? $id)
📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between eaffdc1 and 7c4c056.

⛔ Files ignored due to path filters (1)
  • composer.lock is excluded by !**/*.lock
📒 Files selected for processing (35)
  • .github/workflows/tests.yml (1 hunks)
  • Dockerfile (2 hunks)
  • composer.json (1 hunks)
  • docker-compose.yml (3 hunks)
  • src/Database/Adapter.php (3 hunks)
  • src/Database/Adapter/Pool.php (3 hunks)
  • src/Database/Adapter/SQL.php (2 hunks)
  • src/Database/Database.php (25 hunks)
  • src/Database/Validator/Index.php (6 hunks)
  • src/Database/Validator/Key.php (2 hunks)
  • src/Database/Validator/Label.php (1 hunks)
  • src/Database/Validator/Queries/Documents.php (2 hunks)
  • src/Database/Validator/Query/Filter.php (3 hunks)
  • src/Database/Validator/Query/Order.php (2 hunks)
  • src/Database/Validator/Query/Select.php (2 hunks)
  • src/Database/Validator/Sequence.php (1 hunks)
  • src/Database/Validator/Structure.php (4 hunks)
  • src/Database/Validator/UID.php (1 hunks)
  • tests/e2e/Adapter/MongoDBTest.php (1 hunks)
  • tests/e2e/Adapter/Scopes/AttributeTests.php (18 hunks)
  • tests/e2e/Adapter/Scopes/CollectionTests.php (1 hunks)
  • tests/e2e/Adapter/Scopes/DocumentTests.php (32 hunks)
  • tests/e2e/Adapter/Scopes/GeneralTests.php (2 hunks)
  • tests/e2e/Adapter/Scopes/IndexTests.php (5 hunks)
  • tests/e2e/Adapter/Scopes/PermissionTests.php (2 hunks)
  • tests/e2e/Adapter/Scopes/SpatialTests.php (15 hunks)
  • tests/e2e/Adapter/SharedTables/MongoDBTest.php (1 hunks)
  • tests/resources/mongo/entrypoint.sh (1 hunks)
  • tests/resources/mongo/mongo-keyfile (1 hunks)
  • tests/unit/QueryTest.php (0 hunks)
  • tests/unit/Validator/IndexTest.php (8 hunks)
  • tests/unit/Validator/KeyTest.php (1 hunks)
  • tests/unit/Validator/LabelTest.php (1 hunks)
  • tests/unit/Validator/PermissionsTest.php (3 hunks)
  • tests/unit/Validator/StructureTest.php (2 hunks)
💤 Files with no reviewable changes (1)
  • tests/unit/QueryTest.php
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2025-07-30T19:17:53.630Z
Learnt from: ArnabChatterjee20k
PR: utopia-php/database#642
File: src/Database/Validator/PartialStructure.php:43-52
Timestamp: 2025-07-30T19:17:53.630Z
Learning: In PartialStructure validator, when filtering for required attributes validation using the $requiredAttributes parameter, $this->attributes should be used instead of the merged $attributes array because this validation is specifically for internal attributes like $createdAt and $updatedAt that are defined in the base Structure class, not collection-specific attributes.

Applied to files:

  • src/Database/Validator/Structure.php
🪛 Checkov (3.2.334)
docker-compose.yml

[medium] 129-130: Basic Auth Credentials

(CKV_SECRET_4)

🪛 PHPMD (2.15.0)
tests/e2e/Adapter/MongoDBTest.php

99-99: Avoid unused parameters such as '$collection'. (undefined)

(UnusedFormalParameter)


99-99: Avoid unused parameters such as '$column'. (undefined)

(UnusedFormalParameter)


104-104: Avoid unused parameters such as '$collection'. (undefined)

(UnusedFormalParameter)


104-104: Avoid unused parameters such as '$index'. (undefined)

(UnusedFormalParameter)

src/Database/Adapter/SQL.php

1565-1565: Avoid unused parameters such as '$collection'. (undefined)

(UnusedFormalParameter)


1570-1570: Avoid unused parameters such as '$collection'. (undefined)

(UnusedFormalParameter)

tests/e2e/Adapter/SharedTables/MongoDBTest.php

102-102: Avoid unused parameters such as '$collection'. (undefined)

(UnusedFormalParameter)


102-102: Avoid unused parameters such as '$column'. (undefined)

(UnusedFormalParameter)


107-107: Avoid unused parameters such as '$collection'. (undefined)

(UnusedFormalParameter)


107-107: Avoid unused parameters such as '$index'. (undefined)

(UnusedFormalParameter)

🔇 Additional comments (33)
src/Database/Validator/Label.php (1)

7-7: LGTM! Past feedback addressed.

The message now references self::KEY_MAX_LENGTH dynamically instead of hard-coding "255", aligning with the parent class pattern and ensuring consistency across validators.

src/Database/Validator/UID.php (1)

16-16: LGTM!

The description correctly references self::KEY_MAX_LENGTH for dynamic max-length messaging, ensuring consistency with the updated constant.

tests/unit/Validator/KeyTest.php (1)

69-71: LGTM!

The boundary tests correctly validate the 255-character maximum length, aligning with the new KEY_MAX_LENGTH constant.

tests/unit/Validator/LabelTest.php (1)

61-63: LGTM!

The boundary tests correctly reflect the 255-character limit, consistent with the updated KEY_MAX_LENGTH constant.

src/Database/Validator/Key.php (3)

11-14: LGTM!

The new KEY_MAX_LENGTH constant centralizes the maximum key length value, enabling dynamic references in error messages and validation logic across validators.


19-19: LGTM!

The error message correctly references self::KEY_MAX_LENGTH dynamically, ensuring consistency and maintainability.


84-86: LGTM!

The length check correctly enforces the 255-character maximum using self::KEY_MAX_LENGTH, replacing the previous hard-coded limit.

tests/unit/Validator/PermissionsTest.php (2)

251-251: LGTM!

The error message assertions correctly reflect the updated 255-character maximum length, aligning with the new KEY_MAX_LENGTH constant in the Key validator.

Also applies to: 253-253, 255-255, 259-259, 261-261, 263-263, 269-269, 281-281, 291-291, 293-293


265-269: LGTM!

The boundary tests correctly validate the 255-character maximum length for role identifiers, consistent with the updated constant.

tests/resources/mongo/entrypoint.sh (1)

1-12: LGTM! Keyfile setup and MongoDB startup are correct.

The script properly handles the MongoDB keyfile setup:

  • Copies the keyfile from /tmp to /etc/mongo-keyfile with restricted permissions (400).
  • Sets ownership to mongodb:mongodb for security.
  • Uses set -e to ensure the script exits on any error.
  • Delegates to MongoDB's standard docker-entrypoint.sh with appropriate arguments for replica set, authentication, and keyfile.

The logic is sound and follows MongoDB security best practices.

.github/workflows/tests.yml (1)

73-87: LGTM! Test matrix expansion for MongoDB is appropriate.

The addition of MongoDB and SharedTables/MongoDB to the adapter test matrix ensures comprehensive coverage of the new MongoDB support. The change is straightforward and aligns with the PR objectives.

Dockerfile (2)

36-40: LGTM! MongoDB extension installation is correct.

The MongoDB extension installation and enablement are properly integrated:

  • Uses pecl install mongodb-$PHP_MONGODB_VERSION to install the extension.
  • Enables the extension with docker-php-ext-enable mongodb.
  • Cleans up build dependencies and cache with apk del and rm -rf /var/cache/apk/*.

The approach follows Docker best practices for minimizing image size.


20-20: MongoDB PHP extension version is up-to-date
PHP_MONGODB_VERSION="2.1.1" is the latest stable release (June 13, 2025) and supports PHP 8.3.19.

src/Database/Validator/Structure.php (4)

109-110: LGTM! Feature flag for attribute validation is well-designed.

The addition of the $supportForAttributes parameter enables adapters without traditional attribute schemas (e.g., MongoDB) to bypass semantic attribute validation. The default value of true preserves existing behavior for other adapters, ensuring backward compatibility.


255-272: LGTM! Short-circuit logic for required values is correct.

When $supportForAttributes is false, the method correctly returns true immediately, bypassing the required attribute checks. This aligns with the design for schema-less adapters like MongoDB.


284-295: LGTM! Short-circuit logic for unknown attributes is correct.

When $supportForAttributes is false, the method correctly returns true immediately, bypassing the unknown attribute detection. This is appropriate for schema-less adapters.


368-372: LGTM! Conditional error reporting for unknown types is correct.

The error message for unknown attribute types is only generated when $supportForAttributes is true. This prevents false positives for schema-less adapters that may use custom or flexible types.

src/Database/Validator/Index.php (6)

33-90: LGTM! New capability flags and constructor parameters are well-structured.

The addition of $supportForAttributes, $multipleFulltextIndexSupport, $identicalIndexSupport, and $indexes provides fine-grained control over index validation based on adapter capabilities. The constructor signature is clear, with sensible defaults that preserve backward compatibility.


109-114: LGTM! Attribute existence check respects supportForAttributes flag.

When $supportForAttributes is false, the validator skips checking if attributes exist in the collection, which is appropriate for schema-less adapters like MongoDB.


157-170: LGTM! Fulltext index type validation respects supportForAttributes flag.

The check correctly short-circuits when $supportForAttributes is false, bypassing type validation for fulltext indexes. This is appropriate for adapters that handle types differently.


178-223: LGTM! Array index validation respects supportForAttributes flag.

The method correctly short-circuits when $supportForAttributes is false, bypassing array-specific index validation. This is appropriate for schema-less adapters.


345-352: LGTM! New validation checks are properly integrated.

The calls to checkMultipleFulltextIndex and checkIdenticalIndex are correctly placed in the validation flow, ensuring these checks are performed when validating indexes.


384-403: LGTM! Multiple fulltext index validation is correct.

The checkMultipleFulltextIndex method properly:

  • Short-circuits when $multipleFulltextIndexSupport is true.
  • Skips self-comparison by checking $existingIndex->getId() === $index->getId() (lines 392-394).
  • Rejects additional fulltext indexes when the adapter doesn't support them.

This addresses the previous review concern about self-conflict.

tests/e2e/Adapter/Scopes/IndexTests.php (4)

165-177: LGTM! Index validator instantiation updated correctly.

The instantiation of Index validator now includes the new $indexes array and three capability flags (getSupportForIndexArray, getSupportForSpatialAttributes, etc.), aligning with the updated validator signature.


247-277: LGTM! Conditional validation logic handles adapter capabilities correctly.

The test properly:

  • Instantiates the validator with adapter capability flags.
  • Uses conditional assertions (lines 263-267, 271-276) to verify behavior based on $supportForAttributes.
  • Expects different error messages depending on adapter capabilities.

This ensures the test suite accommodates both schema-based and schema-less adapters.


329-347: LGTM! Index length tests use adapter's max length.

The tests correctly reference $database->getAdapter()->getMaxIndexLength() to validate index length constraints, ensuring tests respect adapter-specific limits.


510-553: LGTM! Multiple fulltext index validation test is comprehensive.

The test properly:

  • Skips when fulltext is not supported.
  • Creates a collection with two string attributes.
  • Creates the first fulltext index.
  • Attempts to create a second fulltext index and verifies success/failure based on getSupportForMultipleFulltextIndexes().

The test logic is sound and exercises the new checkMultipleFulltextIndex validation.

tests/resources/mongo/mongo-keyfile (1)

1-17: LGTM! MongoDB keyfile is appropriate for test infrastructure.

The keyfile contains random data suitable for MongoDB replica set authentication in a test environment. This is correctly referenced by docker-compose.yml and used by entrypoint.sh to enable authentication.

Important: Ensure this keyfile is only used in test/development environments and never in production.

composer.json (1)

38-43: Verify utopia-php/mongo package and ext-mongodb requirements.

  • Confirm that utopia-php/mongo:0.10.* exists as the latest stable release on Packagist and is compatible with PHP 8.1+ (GH repo lookup returned 404).
  • Verify that utopia-php/pools:0.8.* aligns with the latest v0.8.2.
  • Ensure installation docs note the ext-mongodb extension requirement.
src/Database/Database.php (4)

6483-6489: Cursor pre‑encode and pre‑cast: LGTM.

Encoding and applying castingBefore to the cursor aligns it with adapter expectations.

Ensure adapter’s castingBefore accepts a Document and returns a Document consistently across adapters.


6705-6714: Auth skip in sum(): LGTM.

Mirrors behavior in count(). Good consistency.


46-46: Public constant rename is a breaking change: no internal usages of Database::VAR_OBJECT_ID or static calls to Database::convertQuery/convertQueries were found. Confirm any external consumers or extensions referencing these are updated to VAR_UUID7 and the new instance‐method calls.


7140-7154: No static invocations of convertQuery/convertQueries detected — automated search found no Database::convertQuery or Database::convertQueries calls; all usages already use instance methods.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (9)
src/Database/Database.php (9)

3549-3564: Add casting before relationship populate.

Line 3538 already feeds the adapter’s raw document straight into populateDocumentsRelationships. Because populate mutates documents (sets relationship keys) and now the adapter transforms after this block (Line 3550), the relationship handlers operate on uncasted data while the rest of the flow gets casted data. Call castingAfter before populating so both the relationship resolver and the decode step use coherent types:

-        if ($document->isEmpty()) {
-            return $document;
-        }
-
-        $document = $this->adapter->castingAfter($collection, $document);
+        if ($document->isEmpty()) {
+            return $document;
+        }
+
+        $document = $this->adapter->castingAfter($collection, $document);

Also move the populate call below this block. Otherwise, the resolver still sees uncasted values (e.g. Mongo UTCDateTime vs string).


3725-3734: Relationship queue sees uncast documents.

populateDocumentsRelationships now feeds populateSingleRelationshipBatch documents that haven’t gone through adapter->castingAfter, so Mongo returns UTCDateTime objects and strings. Apply casting before the queue loop starts so all relationship branch logic runs on the adapter-normalized shapes.


4291-4295: Ensure createDocument returns cast output.

After adapter->createDocument we run populate (already casting inside) but when relationships are skipped we never call castingAfter before returning, so callers get raw adapter values. Call castingAfter right after the create (before optional populate) to keep behaviour consistent regardless of relationship resolution.


4370-4406: Batch create should cast prior to callbacks.

Each chunk updates $batch from adapter and only calls castingAfter inside the loop, but subsequent logic (populate + onNext) mixes casted and raw documents. Move adapter->castingAfter right after fetching sequences and before population so all downstream code sees normalized docs.


5136-5153: Bulk update casting order wrong.

$encoded = encode(...) then immediately castingBefore on encoded doc, but installed change from previous review ensures encoded result is passed. Verify we pass encoded doc into castingBefore, not original. Current code uses $encoded but then writes back castingBefore(..., $encoded); dropping encoded modifications that casting might return. Assign result to $batch[$index] but we should also keep $encoded for Adapter update call. Confirm logic doesn’t reuse mutated $batch later without castingAfter before decode.


5798-5849: Upsert path casts old/doc before storing Change.

$old and $document both passed through castingBefore prior to storing Change, but once adapter upserts, returned docs go through castingAfter. However we mutate $old (original) by calling castingBefore, which means later handing onNext($doc, $old) returns pre-cast (UTC) data rather than original user-supplied. Need clones or avoid mutating $old.


6661-6663: Deletion updates skip castingAfter.

When deleting documents via deleteDocuments, the $batch documents returned from adapter (or original find) aren’t cast after relationships removed. Prior to change they were already decoded; now with adapter castingBefore/After we removed some calls? Need to ensure deleteDocuments operate on casted docs before invoking callbacks.


7036-7071: Authorization skip flag scope.

$skipAuth introduced but only set if read permission passes; missing default false. Uninitialized variable may lead to undefined. Set upfront.


7953-8145: Relationship query conversion over-applies filters.

When relationship filters mix multiple relations, the code merges queries but loses boolean operator semantics (e.g. OR). This logic always ANDs results via equal $id. Risk of incorrect query when mixing query groups. Without regression tests risk.

📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between aef4c2b and 73d2c29.

📒 Files selected for processing (1)
  • src/Database/Database.php (31 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
src/Database/Database.php (4)
src/Database/Adapter/Mongo.php (15)
  • getMaxIndexLength (2996-2999)
  • getInternalIndexesKeys (3006-3009)
  • getSupportForIndexArray (2539-2542)
  • getSupportForSpatialAttributes (2796-2799)
  • getSupportForSpatialIndexNull (2806-2809)
  • getSupportForSpatialIndexOrder (2827-2830)
  • getSupportForAttributes (2570-2573)
  • getSupportForMultipleFulltextIndexes (2863-2866)
  • getSupportForIdenticalIndexes (2873-2876)
  • castingAfter (1158-1212)
  • castingBefore (1221-1273)
  • updateDocument (1375-1399)
  • sum (2131-2173)
  • getSupportForUTCCasting (2554-2557)
  • setUTCDatetime (2559-2562)
src/Database/Adapter.php (15)
  • getMaxIndexLength (876-876)
  • getInternalIndexesKeys (1291-1291)
  • getSupportForIndexArray (935-935)
  • getSupportForSpatialAttributes (1057-1057)
  • getSupportForSpatialIndexNull (1064-1064)
  • getSupportForSpatialIndexOrder (1078-1078)
  • getSupportForAttributes (914-914)
  • getSupportForMultipleFulltextIndexes (1106-1106)
  • getSupportForIdenticalIndexes (1114-1114)
  • castingAfter (1355-1355)
  • castingBefore (1347-1347)
  • updateDocument (725-725)
  • sum (814-814)
  • getSupportForUTCCasting (1369-1369)
  • setUTCDatetime (1377-1377)
src/Database/Adapter/Pool.php (15)
  • getMaxIndexLength (308-311)
  • getInternalIndexesKeys (488-491)
  • getSupportForIndexArray (338-341)
  • getSupportForSpatialAttributes (428-431)
  • getSupportForSpatialIndexNull (433-436)
  • getSupportForSpatialIndexOrder (523-526)
  • getSupportForAttributes (323-326)
  • getSupportForMultipleFulltextIndexes (543-546)
  • getSupportForIdenticalIndexes (548-551)
  • castingAfter (578-581)
  • castingBefore (573-576)
  • updateDocument (238-241)
  • sum (268-271)
  • getSupportForUTCCasting (588-591)
  • setUTCDatetime (593-596)
src/Database/Adapter/SQL.php (14)
  • getMaxIndexLength (1845-1851)
  • getInternalIndexesKeys (1895-1898)
  • getSupportForIndexArray (1460-1463)
  • getSupportForSpatialAttributes (1490-1493)
  • getSupportForSpatialIndexNull (1500-1503)
  • getSupportForSpatialIndexOrder (1510-1513)
  • getSupportForAttributes (896-899)
  • getSupportForMultipleFulltextIndexes (1530-1533)
  • getSupportForIdenticalIndexes (1540-1543)
  • castingAfter (1570-1573)
  • castingBefore (1565-1568)
  • sum (2660-2723)
  • getSupportForUTCCasting (1555-1558)
  • setUTCDatetime (1560-1563)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (13)
  • GitHub Check: Adapter Tests (SharedTables/MongoDB)
  • GitHub Check: Adapter Tests (SharedTables/SQLite)
  • GitHub Check: Adapter Tests (SharedTables/Postgres)
  • GitHub Check: Adapter Tests (SharedTables/MariaDB)
  • GitHub Check: Adapter Tests (Pool)
  • GitHub Check: Adapter Tests (SharedTables/MySQL)
  • GitHub Check: Adapter Tests (SQLite)
  • GitHub Check: Adapter Tests (Postgres)
  • GitHub Check: Adapter Tests (MySQL)
  • GitHub Check: Adapter Tests (MongoDB)
  • GitHub Check: Adapter Tests (Mirror)
  • GitHub Check: Adapter Tests (MariaDB)
  • GitHub Check: Unit Test
🔇 Additional comments (4)
src/Database/Database.php (4)

5136-5160: Ensure castingAfter before decode in bulk update.

For each $doc new code casts after adapter call, but ensures document gets castingAfter before decode. Confirm — yes line 5150. However need to ensure docs inserted back into $batch are the casted ones before adapter->updateDocuments returns? Wait doc is mutated after adapter update. OK.


6812-6827: Cursor castingBefore returns arrays lacking $id.

We convert cursor document to encoded form, then castingBefore and call getArrayCopy(). That loses $collection / other keys after encode (since encode returns Document). But likely fine. However castingBefore returns Document — using getArrayCopy should still include keys. Need to ensure encode returns Document not mutated? Probably safe.


7513-7526: convertQueries recursion missing clone?

$query = $this->convertQuery but result assigned; OK. But convertQuery now uses adapter UTCCasting. Need to ensure this doesn’t mutate shared Query object used elsewhere? Acceptable.


8032-8083: processNestedRelationshipPath may skip decode/encode.

The function uses find which returns decoded documents (with casting). Later retrieving IDs is fine. Skip comment.

Comment on lines +4935 to +4940
$document = $this->adapter->castingBefore($collection, $document);

$this->adapter->updateDocument($collection, $id, $document, $skipPermissionsUpdate);

$document = $this->adapter->castingAfter($collection, $document);

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

Update path double casts or misses casts.

Inside update transaction we cast both before and after adapter call while still in transaction returning $document. Later, outside, we skip castingAfter unless relationships run. Apply a consistent pattern: cast once after adapter mutation and before returning, and ensure outer path receives that. Current mix risks double casting or skipping for non-relationship updates.

🤖 Prompt for AI Agents
In src/Database/Database.php around lines 4935-4940: the current update flow
calls castingBefore before adapter->updateDocument and castingAfter both inside
the transaction and conditionally again outside, which can cause double-casting
or missed casts for non-relationship updates; change the flow to remove the
pre-update castingBefore call here, call adapter->updateDocument, then call
castingAfter exactly once inside the transaction and return that cast document
so the outer path consumes the already-cast result (and ensure any
relationship-update branch does not re-run castingAfter).

Comment on lines +5146 to +5147
$updates = $this->adapter->castingBefore($collection, $updates);

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

Redundant re-casting of updates template.

After transaction we call castingBefore on $updates, mutating the template shared across documents. That causes next iteration to feed already casted data (e.g. UTCDateTime objects) back into encode, corrupting future updates. Remove this line; only per-document casting should happen.

🤖 Prompt for AI Agents
In src/Database/Database.php around lines 5146-5147, the call to $updates =
$this->adapter->castingBefore($collection, $updates); mutates the shared updates
template and causes already-casted values to be reused across iterations; remove
this line so the template isn't re-cast globally, and ensure castingBefore is
invoked only per-document inside the loop that prepares each document's update
(i.e., call castingBefore($collection, $perDocumentUpdates) within the
per-document processing and do not reassign or mutate the shared $updates
template).

@abnegate abnegate requested a review from fogelito October 5, 2025 04:52
@abnegate abnegate merged commit 8475957 into main Oct 6, 2025
17 checks passed
@abnegate abnegate deleted the feat-mongodb branch October 6, 2025 03:09
@abnegate abnegate restored the feat-mongodb branch October 6, 2025 04:22
@abnegate abnegate mentioned this pull request Oct 6, 2025
abnegate added a commit that referenced this pull request Oct 6, 2025
This reverts commit 8475957, reversing
changes made to e318e54.
@abnegate abnegate mentioned this pull request Oct 6, 2025
@coderabbitai coderabbitai bot mentioned this pull request Nov 20, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants