Skip to content

Conversation

@ArnabChatterjee20k
Copy link
Contributor

@ArnabChatterjee20k ArnabChatterjee20k commented Aug 15, 2025

Previous issue

Case => Permissions not provided in any of the documents in the createDocuments

Problem => Invalid number of token in the sql query

Reason =>

  • For each permission we are binding document to the bindkey
  • if 5 documents and 4 permissions => 20 bindings
  • If shared table => then again 5 keys for each tenant
  • Total binding keys => 25

During binding of the value
we were doing

$sqlPermissions = "
                    INSERT INTO {$this->getSQLTable($name . '_perms')} (_type, _permission, _document {$tenantColumn})
                    VALUES {$permissions};
                ";
   
foreach ($documents as $index => $document) {
    $stmtPermissions->bindValue(":_uid_{$index}", $document->getId());
    if ($this->sharedTables) {
        $stmtPermissions->bindValue(":_tenant_{$index}", $document->getTenant());
    }
}

Here number of bindings => 5 document

Now added all the permissons in the sql statement but the value was going short

Solution

Storing the binding key, values together in a separate array during the permission iteration itself

Summary by CodeRabbit

  • Bug Fixes

    • Improved bulk document creation so per-document permissions are applied consistently, fixing cases where some documents could be missing expected access rights after batch inserts.
  • Tests

    • Added an end-to-end test ensuring bulk-created documents respect individual permissions and that updates and retrievals return only documents with the expected permissions.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Aug 15, 2025

Walkthrough

Consolidates per-document permission parameter bindings inside Adapter/SQL::createDocuments by collecting all permission bind key/value pairs into a single map and binding them in one pass with explicit PDO types; adds an end-to-end test method verifying create/update visibility and permission counts for documents.

Changes

Cohort / File(s) Summary
SQL Adapter Binding Refactor
src/Database/Adapter/SQL.php
Introduce local map $bindValuesPermissions to accumulate per-permission bind keys (e.g., :_uid_{i} and :_tenant_{i} when shared tables are used). Replace the previous per-document binding loop with a single loop that binds each key/value using getPDOType($value). No public API or SQL text changes.
E2E Test: create/update permissions mismatch
tests/e2e/Adapter/Scopes/DocumentTests.php
Add new test method public function testCreateUpdateDocumentsMismatch(): void (appears twice in the diff) that creates a collection, inserts multiple documents with differing permissions, asserts visibility and permission counts after create and update operations, then drops the collection.

Sequence Diagram(s)

sequenceDiagram
  participant Client
  participant SQL_Adapter as Adapter/SQL::createDocuments
  participant DB

  Client->>SQL_Adapter: createDocuments(collection, documents[])
  SQL_Adapter->>SQL_Adapter: Build document rows and permission SQL entries
  SQL_Adapter->>SQL_Adapter: Collect $bindValuesPermissions (:_uid_X, :_tenant_X...)
  SQL_Adapter->>DB: Execute batch INSERT documents (bound params)
  SQL_Adapter->>DB: Execute batch INSERT name_perms (consolidated binds)
  DB-->>SQL_Adapter: Insert results
  SQL_Adapter-->>Client: Return created documents
Loading

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~8 minutes

Possibly related PRs

  • Fix encode permissions #645: Related — also modifies permission handling within createDocuments and may overlap in how permission values are prepared/encoded.

Suggested reviewers

  • abnegate

Poem

A rabbit hops through code so neat,
I gather binds and skip a beat.
Permissions mapped, all in a row,
Tests hop in to see what shows.
Carrots and green checks — off we go! 🥕✨

Tip

🔌 Remote MCP (Model Context Protocol) integration is now available!

Pro plan users can now connect to remote MCP servers from the Integrations page. Connect with popular remote MCPs such as Notion and Linear to add more context to your reviews and chats.

✨ Finishing Touches
  • 📝 Generate Docstrings
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch dat-610-fix-createDocuments-attributes

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.

Support

Need help? Create a ticket on our support page for assistance with any issues or questions.

CodeRabbit Commands (Invoked using PR/Issue comments)

Type @coderabbitai help to get the list of available commands.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

Status, Documentation and Community

  • Visit our Status Page to check the current availability of CodeRabbit.
  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

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: 0

🧹 Nitpick comments (3)
src/Database/Adapter/SQL.php (2)

1900-1910: Bind permission text instead of embedding it in SQL to avoid manual quoting and align with other code paths

Right now _permission is interpolated directly into the SQL (after removing double quotes). While current role strings are constrained, binding them is safer, avoids SQL quoting edge-cases, and matches the binding style used elsewhere (e.g., in update/remove permission diffs).

Suggested refactor (keeps your new binding map and extends it to include _permission):

-                foreach ($document->getPermissionsByType($type) as $permission) {
+                foreach ($document->getPermissionsByType($type) as $permIdx => $permission) {
                     $tenantBind = $this->sharedTables ? ", :_tenant_{$index}" : '';
-                    $permission = \str_replace('"', '', $permission);
-                    $permission = "('{$type}', '{$permission}', :_uid_{$index} {$tenantBind})";
-                    $permissions[] = $permission;
+                    $permBindKey = ":_perm_{$index}_{$type}_{$permIdx}";
+                    $permissions[] = "('{$type}', {$permBindKey}, :_uid_{$index} {$tenantBind})";
+                    $bindValuesPermissions[$permBindKey] = $permission;
                     $bindValuesPermissions[":_uid_{$index}"] = $document->getId();
                     if ($this->sharedTables) {
                         $bindValuesPermissions[":_tenant_{$index}"] = $document->getTenant();
                     }
                 }

This change:

  • Eliminates manual quoting for _permission
  • Preserves your fix for _uid/_tenant binding
  • Keeps column order and SQL shape intact

1936-1941: Consolidated binding with explicit PDO types — looks correct

Iterating $bindValuesPermissions and binding with getPDOType($value) is consistent with the rest of the adapter and removes the prior mismatch between placeholders and bound values.

If you want to reduce redundant work when a document has multiple permissions, you can avoid repeatedly setting the same :_uid_{index}/:_tenant_{index} in the map by guarding with isset(...). It won’t change correctness, just trims a few array writes.

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

372-403: Solid E2E that reproduces the original issue; make Authorization explicit for determinism

The scenario (only the last doc has read permissions, and only that doc is returned post-insert) is a good end-to-end guard for the binding fix. To make the test resilient to global authorization state, set the role explicitly during the assertions:

         $this->assertEquals(3, $database->createDocuments($colName, $docs));
-        // we should get only one document as read permission provided to the last document only
+        // we should get only one document as read permission provided to the last document only
+        \Utopia\Database\Validator\Authorization::setRole(\Utopia\Database\Helpers\Role::any()->toString());
         $addedDocs = $database->find($colName);
         $this->assertCount(1, $addedDocs);
         $doc = $addedDocs[0];
         $this->assertEquals('doc3', $doc->getId());
         $this->assertNotEmpty($doc->getPermissions());
         $this->assertCount(3, $doc->getPermissions());
+        \Utopia\Database\Validator\Authorization::cleanRoles();

Minor note: using Permission::write(Role::any()) together with Permission::update(Role::any()) intentionally exercises deduplication (expecting 3 unique permissions). Nice touch.

📜 Review details

Configuration used: .coderabbit.yaml
Review profile: CHILL
Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between e194da3 and c864ead.

📒 Files selected for processing (2)
  • src/Database/Adapter/SQL.php (3 hunks)
  • tests/e2e/Adapter/Scopes/DocumentTests.php (1 hunks)
🧰 Additional context used
🧬 Code Graph Analysis (1)
src/Database/Adapter/SQL.php (3)
src/Database/Document.php (2)
  • getId (63-66)
  • getTenant (178-187)
src/Database/Adapter/MariaDB.php (1)
  • getPDOType (1835-1843)
src/Database/Adapter/Postgres.php (1)
  • getPDOType (1954-1963)
🔇 Additional comments (1)
src/Database/Adapter/SQL.php (1)

1863-1868: Collected per-permission bind values to fix placeholder/bind mismatch — good change

Initializing and using $bindValuesPermissions ensures every placeholder added to the VALUES list has a matching bound value. This addresses the reported "Invalid number of token" error in bulk permission inserts.

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: 1

🧹 Nitpick comments (2)
tests/e2e/Adapter/Scopes/DocumentTests.php (2)

5852-5860: Make Authorization explicit to avoid order-dependent flakiness

The visibility assertions rely on read("any") being active. Set Role::any explicitly to make the test self-contained and order-independent.

Apply this diff (can be placed right after the batch-ops guard):

         /** @var Database $database */
         $database = static::getDatabase();
 
+        Authorization::cleanRoles();
+        Authorization::setRole(Role::any()->toString());

5892-5904: Strengthen assertions: verify updated keys and that non-permitted docs remain unchanged

After updateDocuments(), assert that:

  • doc3 and doc4 have key updated to 'new doc' (they have update("any")).
  • doc2 remains unchanged (no document-level update permission). Use Authorization::skip to fetch it.

Apply this diff after asserting the update count:

-        $this->assertEquals(2, $database->updateDocuments($colName, new Document(['key' => 'new doc'])));
+        $this->assertEquals(2, $database->updateDocuments($colName, new Document(['key' => 'new doc'])));
+
+        // Verify updated keys for docs with update permissions
+        $doc3After = $database->getDocument($colName, 'doc3');
+        $doc4After = $database->getDocument($colName, 'doc4');
+        $this->assertEquals('new doc', $doc3After->getAttribute('key'));
+        $this->assertEquals('new doc', $doc4After->getAttribute('key'));
+
+        // Verify doc without update permission was not modified
+        $doc2 = Authorization::skip(function () use ($database, $colName) {
+            return $database->getDocument($colName, 'doc2');
+        });
+        $this->assertEquals('doc2', $doc2->getAttribute('key'));
+        $this->assertEquals('test', $doc2->getAttribute('value'));
📜 Review details

Configuration used: .coderabbit.yaml
Review profile: CHILL
Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 1fbdacc and 7c7af88.

📒 Files selected for processing (1)
  • tests/e2e/Adapter/Scopes/DocumentTests.php (1 hunks)
🧰 Additional context used
🧬 Code Graph Analysis (1)
tests/e2e/Adapter/Scopes/DocumentTests.php (4)
tests/e2e/Adapter/Base.php (1)
  • getDatabase (34-34)
src/Database/Adapter/SQLite.php (1)
  • createCollection (141-226)
src/Database/Helpers/Role.php (2)
  • Role (5-178)
  • any (159-162)
src/Database/Document.php (4)
  • Document (12-470)
  • getId (63-66)
  • getPermissions (93-96)
  • getAttribute (224-231)
⏰ 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). (10)
  • GitHub Check: Adapter Tests (SharedTables/Postgres)
  • GitHub Check: Adapter Tests (SharedTables/MySQL)
  • GitHub Check: Adapter Tests (SharedTables/SQLite)
  • GitHub Check: Adapter Tests (SharedTables/MariaDB)
  • GitHub Check: Adapter Tests (Mirror)
  • GitHub Check: Adapter Tests (Postgres)
  • GitHub Check: Adapter Tests (SQLite)
  • GitHub Check: Adapter Tests (MySQL)
  • GitHub Check: Adapter Tests (MariaDB)
  • GitHub Check: Adapter Tests (Pool)

Comment on lines +5852 to +5860
/** @var Database $database */
$database = static::getDatabase();

// with different set of attributes
$colName = "docs_with_diff";
$database->createCollection($colName);
$database->createAttribute($colName, 'key', Database::VAR_STRING, 50, true);
$database->createAttribute($colName, 'value', Database::VAR_STRING, 50, false, 'value');
$permissions = [Permission::read(Role::any()), Permission::write(Role::any()),Permission::update(Role::any())];
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

Add adapter capability guard before using batch updates

This test uses updateDocuments(), which is not supported by all adapters. Other tests in this suite guard on getSupportForBatchOperations(). Add the same guard to avoid false negatives on adapters without batch support.

Apply this diff after obtaining $database:

         /** @var Database $database */
         $database = static::getDatabase();
 
+        if (!$database->getAdapter()->getSupportForBatchOperations()) {
+            $this->expectNotToPerformAssertions();
+            return;
+        }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
/** @var Database $database */
$database = static::getDatabase();
// with different set of attributes
$colName = "docs_with_diff";
$database->createCollection($colName);
$database->createAttribute($colName, 'key', Database::VAR_STRING, 50, true);
$database->createAttribute($colName, 'value', Database::VAR_STRING, 50, false, 'value');
$permissions = [Permission::read(Role::any()), Permission::write(Role::any()),Permission::update(Role::any())];
/** @var Database $database */
$database = static::getDatabase();
if (!$database->getAdapter()->getSupportForBatchOperations()) {
$this->expectNotToPerformAssertions();
return;
}
// with different set of attributes
$colName = "docs_with_diff";
$database->createCollection($colName);
$database->createAttribute($colName, 'key', Database::VAR_STRING, 50, true);
$database->createAttribute($colName, 'value', Database::VAR_STRING, 50, false, 'value');
$permissions = [Permission::read(Role::any()), Permission::write(Role::any()),Permission::update(Role::any())];
🤖 Prompt for AI Agents
In tests/e2e/Adapter/Scopes/DocumentTests.php around lines 5852 to 5860, the
test calls updateDocuments() but does not guard for adapters that lack batch
update support; add a capability check right after obtaining $database: if
(!$this->getSupportForBatchOperations()) { $this->markTestSkipped('Adapter does
not support batch operations'); } so the test is skipped for adapters without
batch support, preventing false negatives.

@abnegate abnegate merged commit b9ba929 into main Aug 15, 2025
15 checks passed
@abnegate abnegate deleted the dat-610-fix-createDocuments-attributes branch August 15, 2025 13:01
@coderabbitai coderabbitai bot mentioned this pull request Aug 26, 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.

3 participants