Skip to content

[#508] Support related criteria paths in SleekDB joins - #509

Merged
armanist merged 8 commits into
quantum-php:masterfrom
armanist:issue/508-sleekdb-related-criteria
May 12, 2026
Merged

armanist merged 8 commits into
quantum-php:masterfrom
armanist:issue/508-sleekdb-related-criteria

Conversation

@armanist

@armanist armanist commented May 11, 2026 •

Copy link
Copy Markdown
Collaborator

Closes #508

Summary by CodeRabbit

  • Bug Fixes

    • SleekDB adapter now correctly filters queries using criteria that reference related/joined models across nested join depths, preserving result semantics.
  • Tests

    • Added comprehensive tests for related-model criteria: immediate joins, deep nested joins, combined root+related criteria, invalid paths, and unsupported OR-style related criteria.
  • Documentation

    • Changelog updated to note the SleekDB related-model criteria fix.

Review Change Stack

@coderabbitai

coderabbitai Bot commented May 11, 2026 •

Copy link
Copy Markdown

Warning

Rate limit exceeded

@armanist has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 30 minutes and 8 seconds before requesting another review.

You’ve run out of usage credits. Purchase more in the billing tab.

⌛ 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.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 69a86bfc-7017-46ee-ae3b-466cfeb02c30

📥 Commits

Reviewing files that changed from the base of the PR and between c448cf6 and 26cc67b.

📒 Files selected for processing (2)
  • src/Database/Adapters/Sleekdb/SleekDbal.php
  • src/Database/Adapters/Sleekdb/Statements/Result.php
📝 Walkthrough

Walkthrough

Adds join-path-aware related-model criteria to the SleekDB adapter: criteria are partitioned into root vs related scopes, applied at appropriate join depths, and parent rows lacking required related data are pruned via post-fetch filtering. DBAL public API unchanged.

Changes

SleekDB Related-Model Criteria Path Filtering

Layer / File(s) Summary
Criteria Scoping State
src/Database/Adapters/Sleekdb/SleekDbal.php
Adds imports and protected state to track rootCriterias, relatedCriteriasByPath, requiredRelatedPaths, criteriaPrepared, and autoSelectedRelatedRoots; updates builder reset and docblock.
Criteria Scoping Logic
src/Database/Adapters/Sleekdb/Statements/RelatedCriteria.php
prepareCriteriaScopes(), join-path discovery, matchRelatedPath(), and helpers split criterias by join path, map dotted columns to deepest matching join path, collect required roots, and reject cross-scope OR combinations.
Builder Orchestration
src/Database/Adapters/Sleekdb/SleekDbal.php
getBuilder() caches a QueryBuilder, calls prepareCriteriaScopesIfNeeded(), uses scope-aware buildSelectForQuery(), and applies modifiers using prepared rootCriterias; resetBuilderState() clears new state.
Join Path-Aware Recursion
src/Database/Adapters/Sleekdb/Statements/Join.php
applyJoins()/applyJoin() accept and propagate currentPath, compute joinPath via buildJoinPath(currentPath, $table), and call applyRelatedCriteriaToJoin() to scope related criterias into join subqueries.
Result Post-Filtering
src/Database/Adapters/Sleekdb/Statements/RelatedCriteria.php
applyRelatedCriteriaPostFilter() prunes parent rows missing required related paths; pathHasData() and isList() traverse nested results; buildSelectForQuery() auto-adds related roots needed for filtering and removeAutoSelectedRelatedRoots() strips them from returned rows.
Retrieval Integration
src/Database/Adapters/Sleekdb/Statements/Result.php
get(), findOne(), findOneBy(), first(), and count() use fetchFilteredResultsFromBuilder() which applies the post-fetch filter before mapping to ORM models.
Test Coverage
tests/Unit/Database/Adapters/Sleekdb/Statements/JoinSleekTest.php
Adds tests for immediate and nested related-model criteria, root+related AND combinations, OR-style rejection (throws RuntimeException), invalid path handling, and root-select/aliased-select behavior.
Changelog
CHANGELOG.md
Documents the fix: SleekDB adapter supports related-model criteria path filtering while preserving the DBAL public API.

Sequence Diagram(s)

sequenceDiagram
  participant Caller
  participant SDBal as SleekDbal
  participant QB as QueryBuilder
  participant Join as JoinTrait
  participant SubQ as JoinSubquery
  participant Result as ResultTrait

  Caller->>SDBal: build query (getBuilder)
  SDBal->>QB: getQueryBuilder() / prepareCriteriaScopesIfNeeded()
  QB->>Join: applyJoins(currentPath='')
  Join->>SubQ: applyJoin(joinPath)
  SubQ->>SubQ: applyRelatedCriteriaToJoin(relatedCriteriasByPath[joinPath])
  QB->>QB: fetch() -> raw rows
  Result->>Result: fetchFilteredResultsFromBuilder() -> applyRelatedCriteriaPostFilter()
  Result->>Caller: return filtered rows / mapped ORM models
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Poem

🐰 I hop through dotted join-lined trails,

I split the rules and follow their tales,
I scope each criterion, deep and neat,
Then prune the rows that fail to meet,
A little rabbit, fetching results complete.

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title directly and clearly references the main objective: adding support for related criteria paths in SleekDB joins, matching the core change across all modified files.
Linked Issues check ✅ Passed All linked issue objectives are met: related-path criteria are routed to join subqueries [#508], DbalInterface API remains unchanged, AND semantics are supported, OR combinations are rejected, and comprehensive tests validate single/deep paths and invalid paths [#508].
Out of Scope Changes check ✅ Passed All changes directly support related-criteria path filtering: SleekDbal refactored for criteria scoping, Join updated for path-aware processing, RelatedCriteria trait added for routing logic, Result refactored for post-filtering, and tests validate the feature.
Docstring Coverage ✅ Passed Docstring coverage is 85.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

Tip

💬 Introducing Slack Agent: The best way for teams to turn conversations into code.

Slack Agent is built on CodeRabbit's deep understanding of your code, so your team can collaborate across the entire SDLC without losing context.

  • Generate code and open pull requests
  • Plan features and break down work
  • Investigate incidents and troubleshoot customer tickets together
  • Automate recurring tasks and respond to alerts with triggers
  • Summarize progress and report instantly

Built for teams:

  • Shared memory across your entire org—no repeating context
  • Per-thread sandboxes to safely plan and execute work
  • Governance built-in—scoped access, auditability, and budget controls

One agent for your entire SDLC. Right inside Slack.

👉 Get started


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.

@codecov

codecov Bot commented May 11, 2026 •

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 94.17040% with 13 lines in your changes missing coverage. Please review.
✅ Project coverage is 90.87%. Comparing base (0b4096a) to head (26cc67b).

Files with missing lines Patch % Lines
...se/Adapters/Sleekdb/Statements/RelatedCriteria.php 90.64% 13 Missing ⚠️
Additional details and impacted files
@@             Coverage Diff              @@
##             master     #509      +/-   ##
============================================
+ Coverage     90.81%   90.87%   +0.05%     
- Complexity     2997     3069      +72     
============================================
  Files           262      263       +1     
  Lines          7906     8096     +190     
============================================
+ Hits           7180     7357     +177     
- Misses          726      739      +13     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@armanist armanist added bug Something isn't working enhancement New feature or request labels May 11, 2026
@armanist armanist added this to the 3.0.0 milestone May 11, 2026

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 2196fa2c70

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src/Database/Adapters/Sleekdb/SleekDbal.php Outdated
Comment thread src/Database/Adapters/Sleekdb/Statements/Result.php Outdated

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Caution

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

⚠️ Outside diff range comments (1)
src/Database/Adapters/Sleekdb/Statements/Result.php (1)

62-97: ⚠️ Potential issue | 🟠 Major | 🏗️ Heavy lift

Avoid calling getBuilder() twice in findOne/findOneBy — it re-applies joins and criteria on the cached query builder.

After this change, the flow becomes:

$this->getBuilder()->where([...]);   // call 1: applies select/joins/rootCriterias/...
$results = $this->fetchFilteredResults(); // call 2 (inside): applies them AGAIN

In SleekDbal::getBuilder() (lines 312–358), the QueryBuilder is cached but operations like select, applyJoins(), where($rootCriterias), having, groupBy, orderBy, skip, and limit are reapplied on every call because they lack an "already applied" guard (only criteriaPrepared has one). This means a second call to getBuilder() will re-invoke applyJoins(), which appends duplicate join() operations to the cached builder, and re-apply $builder->where($this->rootCriterias) even if already added. The existing test suite doesn't catch this because it never combines findOne/findOneBy with joinTo() or prior criteria.

🛠️ Proposed fix — call getBuilder() once and filter the raw result
     public function findOne(int $id): DbalInterface
     {
         try {
-            $this->getBuilder()->where(['id', '=', $id]);
-            $results = $this->fetchFilteredResults();
+            $rawResults = $this->getBuilder()->where(['id', '=', $id])->getQuery()->fetch();
+            $results = $this->applyPostFetchFilters($rawResults);
             $result = $results[0] ?? [];
             $this->updateOrmModel($result);
         } finally {
             $this->resetBuilderState();
         }
         ...
     public function findOneBy(string $column, $value): DbalInterface
     {
         try {
-            $this->getBuilder()->where([$column, '=', $value]);
-            $results = $this->fetchFilteredResults();
+            $rawResults = $this->getBuilder()->where([$column, '=', $value])->getQuery()->fetch();
+            $results = $this->applyPostFetchFilters($rawResults);
             $result = $results[0] ?? [];
             $this->updateOrmModel($result);
         } finally {
             $this->resetBuilderState();
         }

Alternatively, add an appliedToBuilder flag to getBuilder() that mirrors criteriaPrepared for joins, where, having, orderBy, limit, and other operations — but the above is the smallest local fix.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/Database/Adapters/Sleekdb/Statements/Result.php` around lines 62 - 97,
The findOne/findOneBy methods call getBuilder() twice which re-applies
joins/criteria; fix by calling getBuilder() once: assign $builder =
$this->getBuilder(), call $builder->where(...) on that instance, retrieve
results directly from that same $builder (instead of invoking
fetchFilteredResults() which calls getBuilder() again), pick the first result
and pass it to updateOrmModel($result), then call resetBuilderState(); apply
this change in the findOne and findOneBy methods (referencing getBuilder,
fetchFilteredResults, updateOrmModel, findOne, findOneBy).
🧹 Nitpick comments (5)
src/Database/Adapters/Sleekdb/SleekDbal.php (3)

477-501: ⚖️ Poor tradeoff

Defensive note: unserialize($nextItem['model']) is invoked again here.

Static analysis flags the same unserialize() pattern that exists in applyJoin() (Join.php Line 74). The data originates from joinTo() within the same process so the practical risk is low, but if joins ever becomes externally influenced (cached/serialized routes, queued jobs), this becomes an object-injection surface. Consider restricting with unserialize($nextItem['model'], ['allowed_classes' => [DbModel::class, /* known model subclasses */]]) or refactoring joinTo() to store the model reference without serialize()/unserialize() at all.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/Database/Adapters/Sleekdb/SleekDbal.php` around lines 477 - 501,
collectJoinPathsRecursive currently calls unserialize($nextItem['model']) which
can allow object injection; update collectJoinPathsRecursive to deserialize
safely by using the allowed_classes option (restricting to DbModel and any known
subclasses) or, better, refactor joinTo so joins[] stores the model reference
instead of a serialized string; locate collectJoinPathsRecursive and change the
unserialize call to use ['allowed_classes'=>[DbModel::class, /* other models
*/]] or remove serialize/unserialize usage in joinTo/applyJoin so the model is
passed by reference rather than deserialized.

401-410: 💤 Low value

Set criteriaPrepared = true only after the scope split completes successfully.

$this->criteriaPrepared = true; is assigned before the work that can throw (RuntimeException at Line 451). If getBuilder() is re-entered after a caught exception (e.g. by a wrapping layer), the flag will skip re-preparation while rootCriterias/relatedCriteriasByPath/requiredRelatedPaths may be in a partial state. Moving the assignment to the end of the method makes the preparation atomic.

♻️ Suggested move
     protected function prepareCriteriaScopes(): void
     {
         $this->rootCriterias = $this->criterias;
         $this->relatedCriteriasByPath = [];
         $this->requiredRelatedPaths = [];
-        $this->criteriaPrepared = true;
 
         if ($this->joins === [] || $this->criterias === []) {
+            $this->criteriaPrepared = true;
             return;
         }
@@
         $this->requiredRelatedPaths = array_values(array_unique($this->requiredRelatedPaths));
         $this->rootCriterias = $rootCriterias;
+        $this->criteriaPrepared = true;
     }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/Database/Adapters/Sleekdb/SleekDbal.php` around lines 401 - 410, The
method prepareCriteriaScopes() sets $this->criteriaPrepared = true before
performing work that can throw (see RuntimeException in the method and callers
like getBuilder()), which can leave $this->rootCriterias,
$this->relatedCriteriasByPath, and $this->requiredRelatedPaths in a partial
state; move the assignment of $this->criteriaPrepared = true to the very end of
prepareCriteriaScopes() after all splitting/preparation logic completes
successfully so the prepared flag is only set when the scope split finishes
without exceptions.

613-625: 💤 Low value

Optimize isList() to use native PHP 8.1+ array_is_list() when available.

The method reimplements array_is_list() (available since PHP 8.1). Since the project targets PHP 7.4+, use function_exists() to feature-detect and call the native function on 8.1+ runtimes, falling back to the manual loop on earlier versions:

♻️ Suggested optimization
     protected function isList(array $value): bool
     {
+        if (function_exists('array_is_list')) {
+            return array_is_list($value);
+        }
+
         $index = 0;
 
         foreach ($value as $key => $_) {
             if ($key !== $index) {
                 return false;
             }
             $index++;
         }
 
         return true;
     }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/Database/Adapters/Sleekdb/SleekDbal.php` around lines 613 - 625, The
isList method currently reimplements array_is_list; update SleekDbal::isList to
feature-detect and call the native array_is_list() when available by checking
function_exists('array_is_list') and otherwise fall back to the existing loop
implementation in the isList(array $value): bool method so behavior remains the
same on PHP 7.4 while using the native function on PHP 8.1+.
CHANGELOG.md (1)

82-82: 💤 Low value

Consider moving this entry from ### Fixed to ### Added/### Changed.

The other entries in the Fixed section are bug/compat fixes (PHP 8.1/8.4 deprecations, error message assertions, magic-method type hints), whereas this is a new capability for the SleekDB adapter. It would categorize more accurately under Added (or Changed).

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@CHANGELOG.md` at line 82, The changelog entry "SleekDB adapter now supports
related-model criteria path filtering across join depths while preserving DBAL
public API (`#508`)" is a new capability and should be moved out of the "###
Fixed" section into a more appropriate section such as "### Added" or "###
Changed"; update CHANGELOG.md by cutting that exact line from the Fixed block
and pasting it under "### Added" (or "### Changed") so the entry is categorized
correctly.
tests/Unit/Database/Adapters/Sleekdb/Statements/JoinSleekTest.php (1)

296-371: 💤 Low value

Test coverage matches the PR's acceptance criteria; one note about the invalid-path test.

The five new tests cover immediate, deep, AND-combined, OR-rejection, and invalid-path scenarios as outlined in the issue. One observation: testSleekInvalidRelatedCriteriaPathReturnsNoMatches passes because unknown_relation.firstname doesn't match any join path → the criterion falls back to root → SleekDB filters by a non-existent field → 0 rows. That's the documented behavior, but the result also holds for any unknown root column, so a follow-up assertion (e.g. that the user count without the bad criterion is > 0) would make the test explicitly distinguish "invalid related path" from "valid criterion that happens to match nothing".

The OR-rejection test (Line 342) is contingent on prepareCriteriaScopes() actually detecting OR in the nested-array form — see the separate comment on SleekDbal::prepareCriteriaScopes().

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/Unit/Database/Adapters/Sleekdb/Statements/JoinSleekTest.php` around
lines 296 - 371, The invalid-path test
(testSleekInvalidRelatedCriteriaPathReturnsNoMatches) should explicitly prove
the zero-result is due to the bad related-path fallback, not just a non-matching
value: after the existing query that uses
->criteria('unknown_relation.firstname', '=', 'Jane') and asserts count 0, add a
second query using the same TestUserModel (without joining TestProfileModel and
without the invalid related-path criterion) to assert the baseline user count is
> 0 (e.g. $this->assertGreaterThan(0, $usersWithoutBadCriterion->count())). This
makes clear the behavior of prepareCriteriaScopes()/criteria fallback rather
than a dataset that genuinely has no users.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Outside diff comments:
In `@src/Database/Adapters/Sleekdb/Statements/Result.php`:
- Around line 62-97: The findOne/findOneBy methods call getBuilder() twice which
re-applies joins/criteria; fix by calling getBuilder() once: assign $builder =
$this->getBuilder(), call $builder->where(...) on that instance, retrieve
results directly from that same $builder (instead of invoking
fetchFilteredResults() which calls getBuilder() again), pick the first result
and pass it to updateOrmModel($result), then call resetBuilderState(); apply
this change in the findOne and findOneBy methods (referencing getBuilder,
fetchFilteredResults, updateOrmModel, findOne, findOneBy).

---

Nitpick comments:
In `@CHANGELOG.md`:
- Line 82: The changelog entry "SleekDB adapter now supports related-model
criteria path filtering across join depths while preserving DBAL public API
(`#508`)" is a new capability and should be moved out of the "### Fixed" section
into a more appropriate section such as "### Added" or "### Changed"; update
CHANGELOG.md by cutting that exact line from the Fixed block and pasting it
under "### Added" (or "### Changed") so the entry is categorized correctly.

In `@src/Database/Adapters/Sleekdb/SleekDbal.php`:
- Around line 477-501: collectJoinPathsRecursive currently calls
unserialize($nextItem['model']) which can allow object injection; update
collectJoinPathsRecursive to deserialize safely by using the allowed_classes
option (restricting to DbModel and any known subclasses) or, better, refactor
joinTo so joins[] stores the model reference instead of a serialized string;
locate collectJoinPathsRecursive and change the unserialize call to use
['allowed_classes'=>[DbModel::class, /* other models */]] or remove
serialize/unserialize usage in joinTo/applyJoin so the model is passed by
reference rather than deserialized.
- Around line 401-410: The method prepareCriteriaScopes() sets
$this->criteriaPrepared = true before performing work that can throw (see
RuntimeException in the method and callers like getBuilder()), which can leave
$this->rootCriterias, $this->relatedCriteriasByPath, and
$this->requiredRelatedPaths in a partial state; move the assignment of
$this->criteriaPrepared = true to the very end of prepareCriteriaScopes() after
all splitting/preparation logic completes successfully so the prepared flag is
only set when the scope split finishes without exceptions.
- Around line 613-625: The isList method currently reimplements array_is_list;
update SleekDbal::isList to feature-detect and call the native array_is_list()
when available by checking function_exists('array_is_list') and otherwise fall
back to the existing loop implementation in the isList(array $value): bool
method so behavior remains the same on PHP 7.4 while using the native function
on PHP 8.1+.

In `@tests/Unit/Database/Adapters/Sleekdb/Statements/JoinSleekTest.php`:
- Around line 296-371: The invalid-path test
(testSleekInvalidRelatedCriteriaPathReturnsNoMatches) should explicitly prove
the zero-result is due to the bad related-path fallback, not just a non-matching
value: after the existing query that uses
->criteria('unknown_relation.firstname', '=', 'Jane') and asserts count 0, add a
second query using the same TestUserModel (without joining TestProfileModel and
without the invalid related-path criterion) to assert the baseline user count is
> 0 (e.g. $this->assertGreaterThan(0, $usersWithoutBadCriterion->count())). This
makes clear the behavior of prepareCriteriaScopes()/criteria fallback rather
than a dataset that genuinely has no users.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: a97f2d2a-f59f-4d89-b7e3-ed5d2014d175

📥 Commits

Reviewing files that changed from the base of the PR and between 0b4096a and 2196fa2.

📒 Files selected for processing (5)
  • CHANGELOG.md
  • src/Database/Adapters/Sleekdb/SleekDbal.php
  • src/Database/Adapters/Sleekdb/Statements/Join.php
  • src/Database/Adapters/Sleekdb/Statements/Result.php
  • tests/Unit/Database/Adapters/Sleekdb/Statements/JoinSleekTest.php

@armanist armanist added the help wanted Extra attention is needed label May 11, 2026

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/Database/Adapters/Sleekdb/SleekDbal.php`:
- Around line 429-461: The partition loop in SleekDbal.php can miss related-path
columns inside grouped criteria (nested arrays), allowing cross-scope ORs to
bypass the existing guard; update the foreach that processes $this->criterias to
also inspect grouped array entries (e.g., when $criteria[0] is itself an array)
by iterating that group and calling matchRelatedPath(...) for any element with a
string column, setting $foundRelated and moving related conditions into
$this->relatedCriteriasByPath[$path] (or immediately throwing the same
RuntimeException when $hasOr is true), and reject or document groups containing
dotted related columns; also add a unit test in JoinSleekTest that asserts
grouped related-path criteria cause the same RuntimeException (or are rejected)
to lock the behavior.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 75428431-46b9-454f-b36e-a42f9f23b3d2

📥 Commits

Reviewing files that changed from the base of the PR and between 2196fa2 and bffc86b.

📒 Files selected for processing (4)
  • CHANGELOG.md
  • src/Database/Adapters/Sleekdb/SleekDbal.php
  • src/Database/Adapters/Sleekdb/Statements/Result.php
  • tests/Unit/Database/Adapters/Sleekdb/Statements/JoinSleekTest.php
✅ Files skipped from review due to trivial changes (1)
  • CHANGELOG.md
🚧 Files skipped from review as they are similar to previous changes (1)
  • tests/Unit/Database/Adapters/Sleekdb/Statements/JoinSleekTest.php

Comment thread src/Database/Adapters/Sleekdb/SleekDbal.php Outdated

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 2

♻️ Duplicate comments (1)
src/Database/Adapters/Sleekdb/Statements/RelatedCriteria.php (1)

56-88: ⚠️ Potential issue | 🟠 Major | 🏗️ Heavy lift

Grouped related criteria still do not route by scope.

This splitter only handles flat top-level predicates. A grouped entry like [['profiles.firstname', '=', 'John'], 'OR', ['profiles.lastname', '=', 'Doe']] hits the !is_string($criteria[0]) branch, gets pushed into $rootCriterias, and never reaches $relatedCriteriasByPath. That means mixed-scope groups can still bypass the OR guard, while the blanket $foundRelated && $hasOr check also rejects valid ORs that stay inside a single related path. Please recurse through grouped criteria and only reject groups that actually mix scopes.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/Database/Adapters/Sleekdb/Statements/RelatedCriteria.php` around lines 56
- 88, The loop over $this->criterias currently treats grouped predicates as
opaque and pushes them into $rootCriterias, so grouped related predicates never
get routed to $this->relatedCriteriasByPath and OR-mixing checks are wrong;
update the loop to detect when a $criteria is itself a grouped array (e.g. first
element is an array of predicates) and iterate that inner group, using
$this->matchRelatedPath($column, $joinPaths) for each predicate to classify it
into root vs related (populate $rootCriterias,
$this->relatedCriteriasByPath[$path][] and $this->requiredRelatedPaths
accordingly), propagate OR tokens inside the group to $hasOr, set $foundRelated
only when any predicate matched a related path, and only throw the
RuntimeException when a single group actually mixes scopes (i.e. when predicates
within the same group resolve to both root and related paths) or when top-level
mixing occurs across groups; keep using the existing symbols $this->criterias,
matchRelatedPath, $rootCriterias, $this->relatedCriteriasByPath,
$this->requiredRelatedPaths, $foundRelated and $hasOr to locate and modify the
logic.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/Database/Adapters/Sleekdb/SleekDbal.php`:
- Around line 308-314: getBuilder() reuses a cached QueryBuilder but calls
applyBuilderModifiers($builder) every time, causing modifiers
(where/join/orderBy/pagination) to be replayed on subsequent calls; fix by
ensuring modifiers are applied only once per builder lifecycle or by creating a
fresh builder each call: modify getBuilder() to either (a) call
$this->getQueryBuilder() and then applyBuilderModifiers() only when the builder
was just created (e.g. track a flag like $this->builderPrepared or check if
$this->getQueryBuilder() returned a new instance) or (b) always clone/reset the
builder from getQueryBuilder() so applyBuilderModifiers() operates on a fresh
QueryBuilder; adjust prepareCriteriaScopesIfNeeded()/applyBuilderModifiers()
accordingly to avoid mutating the cached builder multiple times.

In `@src/Database/Adapters/Sleekdb/Statements/Result.php`:
- Around line 50-54: The cloned model returned from the array_map closure is
keeping stale query state (cached $queryBuilder and prepared criteria) — before
calling updateOrmModel on the clone, clear its query state. Modify the closure
in Result.php so each clone has its query-related caches reset (e.g. clear
$queryBuilder and prepared criteria) by calling an explicit reset method (create
one like resetQueryState() if none exists) or by nulling/emptying those
properties on $item prior to updateOrmModel($element).

---

Duplicate comments:
In `@src/Database/Adapters/Sleekdb/Statements/RelatedCriteria.php`:
- Around line 56-88: The loop over $this->criterias currently treats grouped
predicates as opaque and pushes them into $rootCriterias, so grouped related
predicates never get routed to $this->relatedCriteriasByPath and OR-mixing
checks are wrong; update the loop to detect when a $criteria is itself a grouped
array (e.g. first element is an array of predicates) and iterate that inner
group, using $this->matchRelatedPath($column, $joinPaths) for each predicate to
classify it into root vs related (populate $rootCriterias,
$this->relatedCriteriasByPath[$path][] and $this->requiredRelatedPaths
accordingly), propagate OR tokens inside the group to $hasOr, set $foundRelated
only when any predicate matched a related path, and only throw the
RuntimeException when a single group actually mixes scopes (i.e. when predicates
within the same group resolve to both root and related paths) or when top-level
mixing occurs across groups; keep using the existing symbols $this->criterias,
matchRelatedPath, $rootCriterias, $this->relatedCriteriasByPath,
$this->requiredRelatedPaths, $foundRelated and $hasOr to locate and modify the
logic.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 7ff925e6-c99f-4ffd-bdab-727279c147d8

📥 Commits

Reviewing files that changed from the base of the PR and between bffc86b and c448cf6.

📒 Files selected for processing (4)
  • src/Database/Adapters/Sleekdb/SleekDbal.php
  • src/Database/Adapters/Sleekdb/Statements/Join.php
  • src/Database/Adapters/Sleekdb/Statements/RelatedCriteria.php
  • src/Database/Adapters/Sleekdb/Statements/Result.php

Comment thread src/Database/Adapters/Sleekdb/SleekDbal.php
Comment thread src/Database/Adapters/Sleekdb/Statements/Result.php
@armanist
armanist merged commit eb8545f into quantum-php:master May 12, 2026
7 checks passed
@armanist
armanist deleted the issue/508-sleekdb-related-criteria branch May 12, 2026 10:08
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Something isn't working enhancement New feature or request help wanted Extra attention is needed

Projects

None yet

Development

Successfully merging this pull request may close these issues.

SleekDB adapter: support related-model criteria paths without changing public DBAL contract

2 participants