Skip to content

Conversation

@fogelito
Copy link
Contributor

@fogelito fogelito commented Sep 2, 2025

Summary by CodeRabbit

  • Refactor
    • Unified document querying across SQL backends for consistent pagination, permissions, and multi-tenant behavior (find/count/sum).
    • Improved stability and error handling for complex queries (including timeouts) and potential performance gains under load.
    • No action required from end-users or SDKs; expected behavior and public APIs remain unchanged.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Sep 2, 2025

Warning

Rate limit exceeded

@fogelito has exceeded the limit for the number of commits or files that can be reviewed per hour. Please wait 1 minutes and 58 seconds before requesting another review.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

📥 Commits

Reviewing files that changed from the base of the PR and between e4f6693 and c2fe9b0.

📒 Files selected for processing (1)
  • src/Database/Adapter/SQL.php (2 hunks)

Walkthrough

Removed adapter-level implementations of find, count, and sum from MariaDB and Postgres, and added unified implementations of those methods to the SQL adapter, handling queries, permissions, tenant scoping, pagination, ordering, aggregations, event triggers, and parameter binding.

Changes

Cohort / File(s) Summary
Adapter removals (MariaDB)
src/Database/Adapter/MariaDB.php
Removed public methods: find, count, sum. Also removed unused imports and adjusted PDO constant references to fully-qualified \PDO.
Adapter removals (Postgres)
src/Database/Adapter/Postgres.php
Removed public methods: find, count, sum.
Unified SQL adapter implementations
src/Database/Adapter/SQL.php
Added public methods: find, count, sum that implement dynamic WHERE construction, permission checks, tenant scoping, cursor-based and offset pagination, ordering, aggregation (count/sum), event triggers (DOCUMENT_FIND/COUNT/SUM), parameter binding (including float handling), and import of TimeoutException.

Sequence Diagram(s)

sequenceDiagram
  autonumber
  participant C as Caller
  participant SQL as SQL Adapter
  participant DB as SQL Engine
  participant EV as Event Dispatcher

  Note over SQL: Unified implementations for find / count / sum

  C->>SQL: find(queries, limit, offset, order, cursor, dir, perm)
  SQL->>SQL: build WHERE (queries + permissions + tenant)
  SQL->>DB: SELECT ... WHERE ... ORDER/LIMIT/OFFSET
  DB-->>SQL: Rows
  SQL->>SQL: map rows → Document[]
  SQL->>EV: trigger DOCUMENT_FIND
  SQL-->>C: Document[]

  C->>SQL: count(queries, max)
  SQL->>SQL: build WHERE (queries + permissions + tenant)
  SQL->>DB: SELECT COUNT(*) FROM (SELECT ... LIMIT max) sub
  DB-->>SQL: Count
  SQL->>EV: trigger DOCUMENT_COUNT
  SQL-->>C: int

  C->>SQL: sum(attribute, queries, max)
  SQL->>SQL: build WHERE (queries + permissions + tenant)
  SQL->>DB: SELECT SUM(attribute) FROM (SELECT ... LIMIT max) sub
  DB-->>SQL: Sum
  SQL->>EV: trigger DOCUMENT_SUM
  SQL-->>C: number
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

Suggested reviewers

  • abnegate

Poem

I thump through code where pathways blend,
Three tunnels joined into one friend.
Queries leap and cursors hum,
Counts and sums now rolled as one.
A rabbit hops — the job's well done. 🐇

✨ Finishing Touches
  • 📝 Generate Docstrings
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch elevate-find-sum-count

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 or @coderabbit 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: 4

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

2569-2572: Unify float binding across find/count/sum

find() formats doubles with configured precision; count() and sum() do not. Apply the same binding to avoid precision drift and query plan differences.

-        foreach ($binds as $key => $value) {
-            $stmt->bindValue($key, $value, $this->getPDOType($value));
-        }
+        foreach ($binds as $key => $value) {
+            if (gettype($value) === 'double') {
+                $stmt->bindValue($key, $this->getFloatPrecision($value), \PDO::PARAM_STR);
+            } else {
+                $stmt->bindValue($key, $value, $this->getPDOType($value));
+            }
+        }

Consider a small helper like bindParams($stmt, array $binds): void to centralize this logic.

Also applies to: 2645-2647


14-14: TimeoutException is imported and documented but never produced

processException() currently returns the PDOException verbatim. Either (a) map driver timeout codes (e.g., HYT00) to TimeoutException in processException(), or (b) drop TimeoutException from the docblock/import.

Would you like a small processException() update that maps common timeout codes for MySQL/MariaDB and Postgres?

Also applies to: 2334-2334


2354-2368: Optional: qualify ORDER BY columns with table alias for consistency

Other predicates are qualified with $alias; ORDER BY isn’t. Not an immediate bug here (single-table), but qualifying improves clarity.

-            $orders[] = "{$this->quote($attribute)} {$direction}";
+            $orders[] = "{$this->quote($alias)}.{$this->quote($attribute)} {$direction}";
📜 Review details

Configuration used: Path: .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 87fb55e and 73ffad0.

📒 Files selected for processing (3)
  • src/Database/Adapter/MariaDB.php (0 hunks)
  • src/Database/Adapter/Postgres.php (0 hunks)
  • src/Database/Adapter/SQL.php (2 hunks)
💤 Files with no reviewable changes (2)
  • src/Database/Adapter/Postgres.php
  • src/Database/Adapter/MariaDB.php
🧰 Additional context used
🧠 Learnings (1)
📓 Common learnings
Learnt from: ArnabChatterjee20k
PR: utopia-php/database#613
File: src/Database/Adapter/Postgres.php:1254-1319
Timestamp: 2025-07-01T11:31:37.438Z
Learning: In PostgreSQL adapter methods like getUpsertStatement, complexity for database-specific SQL generation is acceptable when the main business logic is properly separated in the parent SQL adapter class, following the adapter pattern where each database adapter handles its own SQL syntax requirements.
🧬 Code graph analysis (1)
src/Database/Adapter/SQL.php (5)
src/Database/Database.php (5)
  • Database (37-7226)
  • find (6188-6307)
  • count (6404-6440)
  • trigger (596-614)
  • sum (6455-6484)
src/Database/Exception/Timeout.php (1)
  • Timeout (7-9)
src/Database/Query.php (4)
  • Query (8-1047)
  • limit (609-612)
  • offset (620-623)
  • getAttribute (156-159)
src/Database/Validator/Authorization.php (2)
  • Authorization (7-225)
  • getRoles (101-104)
src/Database/Adapter.php (8)
  • find (802-802)
  • filter (1175-1184)
  • count (825-825)
  • getTenantQuery (1266-1266)
  • getAttributeSelections (1151-1166)
  • getAttributeProjection (1143-1143)
  • trigger (450-460)
  • sum (814-814)
🪛 GitHub Actions: CodeQL
src/Database/Adapter/SQL.php

[error] 2461-2461: Access to constant PARAM_STR on an unknown class Utopia\Database\Adapter\PDO.

⏰ 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). (1)
  • GitHub Check: Setup & Build Docker Image

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

Caution

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

⚠️ Outside diff range comments (2)
src/Database/Adapter/MariaDB.php (2)

1471-1483: OR groups are internally joined with AND — breaks boolean logic.

Inside the TYPE_OR branch you still glue subconditions with ' AND '. This flips OR semantics.

Apply:

-            case Query::TYPE_OR:
-            case Query::TYPE_AND:
+            case Query::TYPE_OR:
+            case Query::TYPE_AND:
                 $conditions = [];
                 /* @var $q Query */
                 foreach ($query->getValue() as $q) {
                     $conditions[] = $this->getSQLCondition($q, $binds, $attributes);
                 }
 
                 $method = strtoupper($query->getMethod());
-
-                return empty($conditions) ? '' : ' '. $method .' (' . implode(' AND ', $conditions) . ')';
+                $innerGlue = $query->getMethod() === Query::TYPE_OR ? ' OR ' : ' AND ';
+                return empty($conditions) ? '' : ' ' . $method . ' (' . implode($innerGlue, $conditions) . ')';

890-915: Avoid string interpolation in permissions INSERT — parameterize to prevent SQL injection and quoting bugs.

_permissions values are injected into SQL with quotes; sanitize only removes double quotes, leaving single quotes/injection risks. Use bound parameters.

-            $permissions = [];
-            foreach (Database::PERMISSIONS as $type) {
-                foreach ($document->getPermissionsByType($type) as $permission) {
-                    $tenantBind = $this->sharedTables ? ", :_tenant" : '';
-                    $permission = \str_replace('"', '', $permission);
-                    $permission = "('{$type}', '{$permission}', :_uid {$tenantBind})";
-                    $permissions[] = $permission;
-                }
-            }
+            $valuesParts = [];
+            $permBinds   = [];
+            $i = 0;
+            foreach (Database::PERMISSIONS as $type) {
+                foreach ($document->getPermissionsByType($type) as $permission) {
+                    $valuesParts[] = $this->sharedTables
+                        ? "(:_type_{$i}, :_perm_{$i}, :_uid, :_tenant_{$i})"
+                        : "(:_type_{$i}, :_perm_{$i}, :_uid)";
+                    $permBinds[":_type_{$i}"] = $type;
+                    $permBinds[":_perm_{$i}"] = \str_replace('"', '', $permission);
+                    if ($this->sharedTables) {
+                        $permBinds[":_tenant_{$i}"] = $document->getTenant();
+                    }
+                    $i++;
+                }
+            }
 
-            if (!empty($permissions)) {
-                $tenantColumn = $this->sharedTables ? ', _tenant' : '';
-                $permissions = \implode(', ', $permissions);
-
-                $sqlPermissions = "
-                    INSERT INTO {$this->getSQLTable($name . '_perms')} (_type, _permission, _document {$tenantColumn})
-                    VALUES {$permissions};
-                ";
-
-                $stmtPermissions = $this->getPDO()->prepare($sqlPermissions);
-                $stmtPermissions->bindValue(':_uid', $document->getId());
-                if ($this->sharedTables) {
-                    $stmtPermissions->bindValue(':_tenant', $document->getTenant());
-                }
-            }
+            if (!empty($valuesParts)) {
+                $tenantColumn = $this->sharedTables ? ', _tenant' : '';
+                $columns = "(_type, _permission, _document{$tenantColumn})";
+                $sqlPermissions = "
+                    INSERT INTO {$this->getSQLTable($name . '_perms')} {$columns}
+                    VALUES " . \implode(', ', $valuesParts) . ";
+                ";
+                $stmtPermissions = $this->getPDO()->prepare($sqlPermissions);
+                $stmtPermissions->bindValue(':_uid', $document->getId());
+                foreach ($permBinds as $k => $v) {
+                    $stmtPermissions->bindValue($k, $v, $this->getPDOType($v));
+                }
+            }
🧹 Nitpick comments (1)
src/Database/Adapter/MariaDB.php (1)

1769-1796: processException likely misses common “table doesn’t exist” code (1146).

Map 42S02/1146 to NotFound to improve DX; keeps existing 1051 path.

         // Unknown collection
-        if ($e->getCode() === '42S02' && isset($e->errorInfo[1]) && $e->errorInfo[1] === 1049) {
+        if ($e->getCode() === '42S02' && isset($e->errorInfo[1]) && $e->errorInfo[1] === 1049) {
             return new NotFoundException('Collection not found', $e->getCode(), $e);
         }
 
         // Unknown collection
         // We have two of same, because docs point to 1051.
         // Keeping previous 1049 (above) just in case it's for older versions
         if ($e->getCode() === '42S02' && isset($e->errorInfo[1]) && $e->errorInfo[1] === 1051) {
             return new NotFoundException('Collection not found', $e->getCode(), $e);
         }
+        // Common MySQL/MariaDB "Table doesn't exist"
+        if ($e->getCode() === '42S02' && isset($e->errorInfo[1]) && $e->errorInfo[1] === 1146) {
+            return new NotFoundException('Collection not found', $e->getCode(), $e);
+        }
📜 Review details

Configuration used: Path: .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 73ffad0 and 41440ae.

📒 Files selected for processing (3)
  • src/Database/Adapter/MariaDB.php (1 hunks)
  • src/Database/Adapter/Postgres.php (0 hunks)
  • src/Database/Adapter/SQL.php (2 hunks)
💤 Files with no reviewable changes (1)
  • src/Database/Adapter/Postgres.php
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/Database/Adapter/SQL.php
🧰 Additional context used
🧠 Learnings (1)
📓 Common learnings
Learnt from: ArnabChatterjee20k
PR: utopia-php/database#613
File: src/Database/Adapter/Postgres.php:1254-1319
Timestamp: 2025-07-01T11:31:37.438Z
Learning: In PostgreSQL adapter methods like getUpsertStatement, complexity for database-specific SQL generation is acceptable when the main business logic is properly separated in the parent SQL adapter class, following the adapter pattern where each database adapter handles its own SQL syntax requirements.
🧬 Code graph analysis (1)
src/Database/Adapter/MariaDB.php (1)
src/Database/PDO.php (1)
  • PDO (12-122)
⏰ 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). (1)
  • GitHub Check: Setup & Build Docker Image
🔇 Additional comments (2)
src/Database/Adapter/MariaDB.php (2)

1635-1639: Good switch to fully-qualified PDO constants.

Using \PDO::PARAM_* avoids alias collisions with Utopia\Database\PDO and ensures correct binding types.


17-18: Verify timeout flag and tenant-scoping usage in SQL::find
Ensure getSupportForTimeouts… exists and that find applies timeout and tenant-scoping internally before centralization is merged.

@abnegate abnegate merged commit bfc010c into main Sep 2, 2025
15 checks passed
@abnegate abnegate deleted the elevate-find-sum-count branch September 2, 2025 07:53
@coderabbitai coderabbitai bot mentioned this pull request Sep 4, 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.

3 participants