Skip to content

Harden tree operations against global scopes; fix zero-offset SQL#3

Merged
glennjacobs merged 1 commit into
mainfrom
fix/global-scope-robustness
Jun 1, 2026
Merged

Harden tree operations against global scopes; fix zero-offset SQL#3
glennjacobs merged 1 commit into
mainfrom
fix/global-scope-robustness

Conversation

@glennjacobs
Copy link
Copy Markdown
Contributor

Summary

Ports and adapts several fixes from upstream kalnoy/nestedset that affect models defining global scopes, plus a SQL-generation bug. Each change is paired with regression tests, and the previously thin coverage of read-side query scopes has been expanded.

All changes live in src/QueryBuilder.php. The full suite passes (114 tests), Pint is clean, and PHPStan reports no errors.

Fixes

Change Upstream Notes
getNodeData() selects only lft/rgt and returns them in a fixed order issue #513 A global scope adding columns (e.g. addSelect) used to leak into the positional [$lft, $rgt] read in moveNode, throwing a TypeError on $rgt - $lft.
Optional $callback on isBroken(), countErrors(), getTotalErrors(), fixTree(), fixSubtree() PR #536 Lets callers customise the integrity-check query — most usefully fn ($q) => $q->withoutGlobalScopes(). Backwards compatible.
withDepth() honours scopes removed from the outer query PR #300 Model::withoutGlobalScope(...)->withDepth() now counts the same ancestor set in the depth subquery.
columnPatch() renders a zero offset as +0 PR #595 Previously emitted the malformed "_rgt"0, which is invalid SQL on stricter drivers such as SQL Server.

Tests

  • tests/GlobalScopeTest.php — covers all four fixes, including direct columnPatch assertions and a demonstration that an interfering global scope disrupts the integrity checks until the callback drops it. Verified to genuinely fail when the source fixes are reverted.
  • tests/QueryScopesTest.php — fills a coverage gap on read-side public API: whereIsLeaf/leaves, hasParent/hasChildren, getNextNode/getPrevNode, inclusive descendant/ancestor queries, inclusive predicates, getBounds, prependToNode, and OR/negation query branches.
  • Two new fixtures (SelectScopedCategory, FilteredCategory) model the global-scope scenarios.

Deliberately out of scope

Upstream PR #514 also blanket-swapped newQuery()newQueryWithoutScopes() in descendants(), ancestors(), newNestedSetQuery() and newScopedQuery(). That changes soft-delete/scoping semantics package-wide (e.g. siblings() would start returning trashed rows), so it's left out as a separate maintainer decision. The targeted getNodeData fix resolves the #513 crash without that risk.

🤖 Generated with Claude Code

Port and adapt fixes from upstream kalnoy/nestedset for issues that
affect models defining global scopes, plus a SQL generation bug.

- getNodeData() now selects only lft/rgt and returns them in a known
  order, so a scope's extra columns can no longer corrupt the
  [$lft, $rgt] read during a move (upstream issue #513).
- isBroken(), countErrors(), getTotalErrors(), fixTree() and
  fixSubtree() accept an optional callback to customise the query,
  most usefully to drop an interfering global scope (upstream PR #536).
- withDepth() honours global scopes removed from the outer query, so
  withoutGlobalScope(...)->withDepth() counts the same ancestors in the
  depth subquery (upstream PR #300).
- columnPatch() renders a zero offset as "+0" instead of concatenating
  it onto the column ("_rgt"0), which is invalid SQL on stricter
  drivers such as SQL Server (upstream PR #595).

Also expands test coverage for previously untested public query scopes
and predicates (leaves, hasParent/hasChildren, getNextNode/getPrevNode,
inclusive descendant/ancestor queries, inclusive predicates, etc.).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@glennjacobs glennjacobs merged commit 3393853 into main Jun 1, 2026
6 checks passed
@glennjacobs glennjacobs deleted the fix/global-scope-robustness branch June 1, 2026 14:46
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