Skip to content

refactor: expose Query::shape() as iterative instance method#4

Merged
abnegate merged 1 commit intomainfrom
CLO-4204-query-fingerprint
Apr 20, 2026
Merged

refactor: expose Query::shape() as iterative instance method#4
abnegate merged 1 commit intomainfrom
CLO-4204-query-fingerprint

Conversation

@lohanidamodar
Copy link
Copy Markdown
Contributor

Follow-up to #3 — per @abnegate's review comment:

If we need an instance anyway, lets make it an instance method shape, with a loop instead of recursion.

Changes

  • Extract the canonical-shape helper out of fingerprint() and into a public instance method Query::shape().
  • Replace the recursive walk with an iterative post-order traversal: preorder-flatten the tree via a stack, then process the reversed list so every node's children are shaped before the node itself.
  • fingerprint() now just calls $query->shape() on each element.

API

Query::equal('name', ['Alice'])->shape();
// => 'equal:name'

$and = new Query(Query::TYPE_AND, '', [
    Query::equal('name', ['Alice']),
    Query::greaterThan('age', 18),
]);
$and->shape();
// => 'and:(equal:name|greaterThan:age)'

Test plan

Added testShape() covering leaf, logical, elemMatch, and 4-level-deep nested queries. Confirms iterative output matches the recursive baseline and that elemMatch's attribute is preserved in its shape.

Mirrors the same refactor applied to utopia-php/database#859.

Per review: make the canonical-shape helper a public instance method on Query (`shape()`) rather than a private static inside `fingerprint()`, and replace the recursive walk with an iterative stack-based post-order traversal.

Added test covering leaf, logical, elemMatch, and 4-level-deep nested shapes to verify iterative equivalence with the previous recursive version.
@greptile-apps
Copy link
Copy Markdown

greptile-apps bot commented Apr 20, 2026

Greptile Summary

This PR extracts a canonical shape computation from fingerprint() into a public instance method Query::shape(), replacing the prior recursive walk with an iterative preorder-flatten + reversed-pass approach. The algorithm is correct: parents are always emitted before their children in the preorder list, so after reversing, every node's children are guaranteed to have their shapes resolved before the node itself is processed. The spl_object_id keying is safe because $nodes holds strong references to every object throughout the traversal.

Confidence Score: 5/5

Safe to merge — no correctness, security, or data-integrity issues found.

All findings are P2 or lower. The core invariant (children resolved before parents in the reversed list) holds by construction of a preorder traversal, and tests cover leaf, logical, elemMatch, and 4-level-deep nesting.

No files require special attention.

Important Files Changed

Filename Overview
src/Query/Query.php Adds public iterative shape() method and updates fingerprint() to delegate to it; traversal logic is algorithmically correct.
tests/Query/QueryTest.php Adds testShape() covering leaf, logical, elemMatch, and 4-level-deep nested queries; all assertions match the algorithm's output.

Reviews (1): Last reviewed commit: "refactor: expose shape() as an iterative..." | Re-trigger Greptile

@abnegate abnegate merged commit 9baaf7a into main Apr 20, 2026
4 checks passed
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.

2 participants