Skip to content

Extract query lib#823

Open
abnegate wants to merge 4 commits intomainfrom
feat-query-lib
Open

Extract query lib#823
abnegate wants to merge 4 commits intomainfrom
feat-query-lib

Conversation

@abnegate
Copy link
Member

@abnegate abnegate commented Mar 3, 2026

Summary by CodeRabbit

  • Chores
    • Added new external dependency for unified query handling and validation.
    • Refactored query class to consolidate operations through shared architecture.
    • Restructured query processing pipeline for improved consistency and maintainability.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Mar 3, 2026

Important

Review skipped

Review was skipped due to path filters

⛔ Files ignored due to path filters (1)
  • composer.lock is excluded by !**/*.lock

CodeRabbit blocks several paths by default. You can override this behavior by explicitly including those paths in the path filters. For example, including **/dist/** will override the default block on the dist directory, by removing the pattern from both the lists.

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

The pull request adds the utopia-php/query library as a Composer dependency via a VCS repository and refactors the Query class to extend the external BaseQuery, removing local constants and delegating parsing logic to the base class.

Changes

Cohort / File(s) Summary
Dependency Management
composer.json
Adds utopia-php/query (version 0.1.*) as a required dependency with a VCS repository pointing to the GitHub source.
Query Class Refactoring
src/Database/Query.php
Converts Query to extend BaseQuery; removes ~50 public constants (TYPE_EQUAL, TYPE_NOT_EQUAL, etc.) and numerous builder methods now inherited; delegates parse() and parseQuery() to base class while wrapping exceptions; updates constructor and internal state handling to align with base class design.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Poem

🐰 A query hops to new terrain,
Where BaseQuery pulls the reins,
Constants fade, methods take flight,
Inheritance sets the sight straight—
Cleaner code in utopia's way! ✨

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'Extract query lib' directly and clearly describes the main change: moving query functionality into an external library dependency (utopia-php/query), which aligns with the significant refactoring shown in the changeset.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feat-query-lib

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

Comment @coderabbitai help to get the list of available commands and usage tips.

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

🧹 Nitpick comments (1)
src/Database/Query.php (1)

62-76: Align cursor helper docblocks with the actual parameter type.

Lines 62 and 73 document Document, but the signature is mixed $value (Lines 65 and 76). Please update phpdoc to match actual behavior.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/Database/Query.php` around lines 62 - 76, The PHPDoc for
Query::cursorAfter and Query::cursorBefore incorrectly types the parameter as
Document while the method signatures accept mixed $value; update the `@param`
annotations in both docblocks to "@param mixed $value" (and keep "@return Query"
unchanged) so the docblocks match the actual signatures for the cursorAfter and
cursorBefore methods on the Query class.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@composer.json`:
- Around line 62-66: Replace the SSH Git URL in the composer.json "repositories"
entry with its HTTPS equivalent to avoid CI SSH auth issues; locate the
"repositories" array and update the url value currently set to
"git@github.com:utopia-php/query.git" to use
"https://github.com/utopia-php/query.git" so installs succeed in non-interactive
CI environments.

In `@src/Database/Query.php`:
- Around line 167-183: The code is using $limit as a fallback for both $offset
and $cursor which is incorrect; update the assignments in the TYPE_OFFSET and
TYPE_CURSOR_AFTER/TYPE_CURSOR_BEFORE branches so they use the actual first value
or null instead of $limit (i.e. change $values[0] ?? $limit to $values[0] ??
null) while keeping the existing checks that ignore subsequent offsets/cursors
and preserving $cursorDirection assignment (references: TYPE_OFFSET,
TYPE_CURSOR_AFTER, TYPE_CURSOR_BEFORE, $offset, $cursor, $values, $limit,
$cursorDirection, Database::CURSOR_AFTER, Database::CURSOR_BEFORE).

---

Nitpick comments:
In `@src/Database/Query.php`:
- Around line 62-76: The PHPDoc for Query::cursorAfter and Query::cursorBefore
incorrectly types the parameter as Document while the method signatures accept
mixed $value; update the `@param` annotations in both docblocks to "@param mixed
$value" (and keep "@return Query" unchanged) so the docblocks match the actual
signatures for the cursorAfter and cursorBefore methods on the Query class.

ℹ️ Review info

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between ba1ee9c and 44c19f9.

⛔ Files ignored due to path filters (1)
  • composer.lock is excluded by !**/*.lock
📒 Files selected for processing (2)
  • composer.json
  • src/Database/Query.php

Comment on lines +62 to +66
"repositories": [
{
"type": "vcs",
"url": "git@github.com:utopia-php/query.git"
}
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

🧩 Analysis chain

🏁 Script executed:

cat -n composer.json | head -70

Repository: utopia-php/database

Length of output: 2871


🏁 Script executed:

python - <<'PY'
import json
from pathlib import Path

data = json.loads(Path("composer.json").read_text())
repos = data.get("repositories", [])
for idx, repo in enumerate(repos):
    url = repo.get("url", "")
    if url.startswith("git@"):
        print(f"Repository[{idx}] uses SSH URL: {url}")
    else:
        print(f"Repository[{idx}] URL type: {url}")
PY

Repository: utopia-php/database

Length of output: 126


Use HTTPS for the VCS repository URL to prevent install failures in CI.

Line 65 uses an SSH URL (git@github.com:utopia-php/query.git), which fails in non-interactive CI environments that lack SSH key configuration. Change to the HTTPS equivalent:

🔧 Proposed fix
     "repositories": [
         {
             "type": "vcs",
-            "url": "git@github.com:utopia-php/query.git"
+            "url": "https://github.com/utopia-php/query.git"
         }
     ],
📝 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
"repositories": [
{
"type": "vcs",
"url": "git@github.com:utopia-php/query.git"
}
"repositories": [
{
"type": "vcs",
"url": "https://github.com/utopia-php/query.git"
}
],
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@composer.json` around lines 62 - 66, Replace the SSH Git URL in the
composer.json "repositories" entry with its HTTPS equivalent to avoid CI SSH
auth issues; locate the "repositories" array and update the url value currently
set to "git@github.com:utopia-php/query.git" to use
"https://github.com/utopia-php/query.git" so installs succeed in non-interactive
CI environments.

Comment on lines +167 to +183
case self::TYPE_OFFSET:
// Keep the 1st offset encountered and ignore the rest
if ($offset !== null) {
break;
}

$offset = $values[0] ?? $limit;
break;
case Query::TYPE_CURSOR_AFTER:
case Query::TYPE_CURSOR_BEFORE:
case self::TYPE_CURSOR_AFTER:
case self::TYPE_CURSOR_BEFORE:
// Keep the 1st cursor encountered and ignore the rest
if ($cursor !== null) {
break;
}

$cursor = $values[0] ?? $limit;
$cursorDirection = $method === Query::TYPE_CURSOR_AFTER ? Database::CURSOR_AFTER : Database::CURSOR_BEFORE;
$cursorDirection = $method === self::TYPE_CURSOR_AFTER ? Database::CURSOR_AFTER : Database::CURSOR_BEFORE;
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 | 🟡 Minor

Fix incorrect fallback variable usage for offset and cursor.

Line 173 and Line 182 fall back to $limit, which is unrelated and can silently corrupt grouped query state.

🔧 Proposed fix
                 case self::TYPE_OFFSET:
@@
-                    $offset = $values[0] ?? $limit;
+                    $offset = $values[0] ?? $offset;
                     break;
@@
                 case self::TYPE_CURSOR_AFTER:
                 case self::TYPE_CURSOR_BEFORE:
@@
-                    $cursor = $values[0] ?? $limit;
+                    $cursor = $values[0] ?? $cursor;
                     $cursorDirection = $method === self::TYPE_CURSOR_AFTER ? Database::CURSOR_AFTER : Database::CURSOR_BEFORE;
                     break;
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/Database/Query.php` around lines 167 - 183, The code is using $limit as a
fallback for both $offset and $cursor which is incorrect; update the assignments
in the TYPE_OFFSET and TYPE_CURSOR_AFTER/TYPE_CURSOR_BEFORE branches so they use
the actual first value or null instead of $limit (i.e. change $values[0] ??
$limit to $values[0] ?? null) while keeping the existing checks that ignore
subsequent offsets/cursors and preserving $cursorDirection assignment
(references: TYPE_OFFSET, TYPE_CURSOR_AFTER, TYPE_CURSOR_BEFORE, $offset,
$cursor, $values, $limit, $cursorDirection, Database::CURSOR_AFTER,
Database::CURSOR_BEFORE).

Add a PHPStan stub for Utopia\Query\Query that declares `@return static`
on all factory methods, so PHPStan correctly resolves return types when
called via the Utopia\Database\Query subclass. Also fix groupByType()
param type and remove dead instanceof checks in parse/parseQuery.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
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.

♻️ Duplicate comments (1)
src/Database/Query.php (1)

161-177: ⚠️ Potential issue | 🟠 Major

Fix fallback leakage from limit into offset/cursor state.

Line 167 and Line 176 incorrectly use $limit as fallback, which can corrupt grouped query output when offset/cursor values are missing.

🔧 Proposed fix
                 case self::TYPE_OFFSET:
@@
-                    $offset = $values[0] ?? $limit;
+                    $offset = $values[0] ?? $offset;
                     break;
@@
                 case self::TYPE_CURSOR_AFTER:
                 case self::TYPE_CURSOR_BEFORE:
@@
-                    $cursor = $values[0] ?? $limit;
+                    $cursor = $values[0] ?? $cursor;
                     $cursorDirection = $method === self::TYPE_CURSOR_AFTER ? Database::CURSOR_AFTER : Database::CURSOR_BEFORE;
                     break;
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/Database/Query.php` around lines 161 - 177, In Query:: (switch handling
TYPE_OFFSET / TYPE_CURSOR_AFTER / TYPE_CURSOR_BEFORE) remove the incorrect
fallback to $limit so $offset and $cursor aren't polluted: instead of assigning
$values[0] ?? $limit, only assign when an explicit value exists (e.g. check
isset($values[0]) or array_key_exists) and otherwise leave $offset/$cursor as
null; keep the first-occurrence guards intact and preserve setting
$cursorDirection when handling TYPE_CURSOR_AFTER vs TYPE_CURSOR_BEFORE
(Database::CURSOR_AFTER / Database::CURSOR_BEFORE).
🧹 Nitpick comments (1)
src/Database/Query.php (1)

53-73: Align cursor helper PHPDoc with the actual mixed parameter type.

Both docblocks still document @param Document $value, but the signatures now accept mixed.

📝 Suggested doc fix
-     * `@param` Document $value
+     * `@param` mixed $value
@@
-     * `@param` Document $value
+     * `@param` mixed $value
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/Database/Query.php` around lines 53 - 73, Update the PHPDoc for the Query
helper methods to match the actual parameter type: in the Query class update the
docblocks for cursorAfter and cursorBefore so the `@param` annotation is "@param
mixed $value" (instead of "@param Document $value") and keep the `@return`
annotation as "Query"; ensure these docblocks sit immediately above the
corresponding methods cursorAfter and cursorBefore to maintain accurate
IDE/typehinting.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Duplicate comments:
In `@src/Database/Query.php`:
- Around line 161-177: In Query:: (switch handling TYPE_OFFSET /
TYPE_CURSOR_AFTER / TYPE_CURSOR_BEFORE) remove the incorrect fallback to $limit
so $offset and $cursor aren't polluted: instead of assigning $values[0] ??
$limit, only assign when an explicit value exists (e.g. check isset($values[0])
or array_key_exists) and otherwise leave $offset/$cursor as null; keep the
first-occurrence guards intact and preserve setting $cursorDirection when
handling TYPE_CURSOR_AFTER vs TYPE_CURSOR_BEFORE (Database::CURSOR_AFTER /
Database::CURSOR_BEFORE).

---

Nitpick comments:
In `@src/Database/Query.php`:
- Around line 53-73: Update the PHPDoc for the Query helper methods to match the
actual parameter type: in the Query class update the docblocks for cursorAfter
and cursorBefore so the `@param` annotation is "@param mixed $value" (instead of
"@param Document $value") and keep the `@return` annotation as "Query"; ensure
these docblocks sit immediately above the corresponding methods cursorAfter and
cursorBefore to maintain accurate IDE/typehinting.

ℹ️ Review info

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 44c19f9 and d942f2b.

📒 Files selected for processing (3)
  • phpstan.neon
  • src/Database/Query.php
  • stubs/Query.stub

Update Query overrides to use `: static` return types matching the
base query package. Remove the phpstan.neon and stubs workaround
since the query package now uses `: static` natively.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
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

♻️ Duplicate comments (1)
src/Database/Query.php (1)

159-169: ⚠️ Potential issue | 🟠 Major

Fix incorrect fallback variable usage for offset and cursor.

Lines 159 and 168 incorrectly fall back to $limit instead of the appropriate variable. This can silently corrupt the grouped query state.

  • Line 159: $offset = $values[0] ?? $limit should use $offset (or null)
  • Line 168: $cursor = $values[0] ?? $limit should use $cursor (or null)
🔧 Proposed fix
                 case self::TYPE_OFFSET:
                     // Keep the 1st offset encountered and ignore the rest
                     if ($offset !== null) {
                         break;
                     }

-                    $offset = $values[0] ?? $limit;
+                    $offset = $values[0] ?? null;
                     break;
                 case self::TYPE_CURSOR_AFTER:
                 case self::TYPE_CURSOR_BEFORE:
                     // Keep the 1st cursor encountered and ignore the rest
                     if ($cursor !== null) {
                         break;
                     }

-                    $cursor = $values[0] ?? $limit;
+                    $cursor = $values[0] ?? null;
                     $cursorDirection = $method === self::TYPE_CURSOR_AFTER ? Database::CURSOR_AFTER : Database::CURSOR_BEFORE;
                     break;
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/Database/Query.php` around lines 159 - 169, The switch handling in
Query.php incorrectly falls back to $limit when assigning $offset and $cursor,
corrupting grouped query state; update the assignments in the cases that set
$offset and $cursor (the branches that assign $offset = $values[0] ?? $limit and
$cursor = $values[0] ?? $limit) to use a proper null fallback instead (e.g.
$offset = $values[0] ?? null and $cursor = $values[0] ?? null), keeping the
surrounding logic for cursorDirection (Database::CURSOR_AFTER /
Database::CURSOR_BEFORE) intact.
🧹 Nitpick comments (1)
src/Database/Query.php (1)

54-65: Consider tightening parameter type hint.

The PHPDoc indicates @param Document $value, but the signature uses mixed. Consider using Document as the type hint for better type safety, or update the PHPDoc to reflect the actual accepted types.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/Database/Query.php` around lines 54 - 65, The PHPDoc for cursorBefore
indicates the parameter is a Document but the method signature (and cursorAfter)
use mixed; update the signatures to use Document instead of mixed for
cursorBefore and cursorAfter (i.e., change the parameter type from mixed to
Document) to match the PHPDoc and improve type safety, or alternatively update
the PHPDoc to reflect mixed if these methods truly accept other types—adjust the
declarations in the Query class (cursorBefore, cursorAfter) so the docblock and
method signatures are consistent.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/Database/Query.php`:
- Around line 42-49: The method parseQuery currently declares a return type of
static but calls parent::parseQuery() which returns BaseQuery; change the method
signature to return BaseQuery instead of static and keep the try/catch that
wraps BaseQueryException into QueryException (i.e., update the return type on
parseQuery to BaseQuery to match parent::parseQuery and ensure the thrown
QueryException still wraps the original BaseQueryException).

---

Duplicate comments:
In `@src/Database/Query.php`:
- Around line 159-169: The switch handling in Query.php incorrectly falls back
to $limit when assigning $offset and $cursor, corrupting grouped query state;
update the assignments in the cases that set $offset and $cursor (the branches
that assign $offset = $values[0] ?? $limit and $cursor = $values[0] ?? $limit)
to use a proper null fallback instead (e.g. $offset = $values[0] ?? null and
$cursor = $values[0] ?? null), keeping the surrounding logic for cursorDirection
(Database::CURSOR_AFTER / Database::CURSOR_BEFORE) intact.

---

Nitpick comments:
In `@src/Database/Query.php`:
- Around line 54-65: The PHPDoc for cursorBefore indicates the parameter is a
Document but the method signature (and cursorAfter) use mixed; update the
signatures to use Document instead of mixed for cursorBefore and cursorAfter
(i.e., change the parameter type from mixed to Document) to match the PHPDoc and
improve type safety, or alternatively update the PHPDoc to reflect mixed if
these methods truly accept other types—adjust the declarations in the Query
class (cursorBefore, cursorAfter) so the docblock and method signatures are
consistent.

ℹ️ Review info

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between d942f2b and b0a1faf.

📒 Files selected for processing (1)
  • src/Database/Query.php

Comment on lines +42 to 49
public static function parseQuery(array $query): static
{
try {
$query = \json_decode($query, true, flags: JSON_THROW_ON_ERROR);
} catch (\JsonException $e) {
throw new QueryException('Invalid query: ' . $e->getMessage());
}

if (!\is_array($query)) {
throw new QueryException('Invalid query. Must be an array, got ' . \gettype($query));
return parent::parseQuery($query);
} catch (BaseQueryException $e) {
throw new QueryException($e->getMessage(), $e->getCode(), $e);
}

return self::parseQuery($query);
}
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 | 🔴 Critical

Same return type issue as parse().

parent::parseQuery() will also return BaseQuery, not static. Apply the same fix pattern.

🔧 Proposed fix
     public static function parseQuery(array $query): static
     {
         try {
-            return parent::parseQuery($query);
+            $parsed = parent::parseQuery($query);
+            return new static($parsed->getMethod(), $parsed->getAttribute(), $parsed->getValues());
         } catch (BaseQueryException $e) {
             throw new QueryException($e->getMessage(), $e->getCode(), $e);
         }
     }
📝 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
public static function parseQuery(array $query): static
{
try {
$query = \json_decode($query, true, flags: JSON_THROW_ON_ERROR);
} catch (\JsonException $e) {
throw new QueryException('Invalid query: ' . $e->getMessage());
}
if (!\is_array($query)) {
throw new QueryException('Invalid query. Must be an array, got ' . \gettype($query));
return parent::parseQuery($query);
} catch (BaseQueryException $e) {
throw new QueryException($e->getMessage(), $e->getCode(), $e);
}
return self::parseQuery($query);
}
public static function parseQuery(array $query): static
{
try {
$parsed = parent::parseQuery($query);
return new static($parsed->getMethod(), $parsed->getAttribute(), $parsed->getValues());
} catch (BaseQueryException $e) {
throw new QueryException($e->getMessage(), $e->getCode(), $e);
}
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/Database/Query.php` around lines 42 - 49, The method parseQuery currently
declares a return type of static but calls parent::parseQuery() which returns
BaseQuery; change the method signature to return BaseQuery instead of static and
keep the try/catch that wraps BaseQueryException into QueryException (i.e.,
update the return type on parseQuery to BaseQuery to match parent::parseQuery
and ensure the thrown QueryException still wraps the original
BaseQueryException).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
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.

1 participant