Skip to content

feat: add Query::fingerprint() for shape-only query hashing#3

Merged
abnegate merged 3 commits intomainfrom
CLO-4204-query-fingerprint
Apr 20, 2026
Merged

feat: add Query::fingerprint() for shape-only query hashing#3
abnegate merged 3 commits intomainfrom
CLO-4204-query-fingerprint

Conversation

@lohanidamodar
Copy link
Copy Markdown
Contributor

Summary

Adds Query::fingerprint(array $queries): string — a static helper that computes a deterministic hash of query structure (method + attribute) with values excluded.

Mirrors the same helper being added in utopia-php/database#859 so the two libraries stay in sync.

Why

Useful for grouping queries by pattern — for example, slow-query analytics where two queries with the same shape but different parameter values should count as the same pattern. Two queries like equal("name", "Alice") and equal("name", "Bob") produce the same fingerprint.

Behavior

  • Accepts raw query strings or already-parsed Query objects
  • Order-independent (sorts shapes before hashing)
  • Returns md5 hash of canonical method:attribute|method:attribute|... string
  • Empty array returns a deterministic hash (md5(''))

Test plan

Added testFingerprint() in tests/Query/QueryTest.php covering:

  • Same shape different values → same hash
  • Different attribute → different hash
  • Different method → different hash
  • Order-independence
  • Accepts parsed Query objects
  • Empty array behavior

All 19 tests pass locally (45 assertions).

Compute a deterministic hash of query structure (method + attribute)
with values excluded. Useful for grouping queries by pattern — e.g.
slow-query analytics where two queries with the same shape but
different parameter values should count as the same pattern.

Accepts raw query strings or parsed Query objects. Order-independent.
@greptile-apps
Copy link
Copy Markdown

greptile-apps bot commented Apr 20, 2026

Greptile Summary

Adds Query::fingerprint(array $queries): string, a static helper that computes a deterministic, value-free md5 hash of query structure for pattern-based grouping (e.g., slow-query analytics). The recursive queryShape() helper correctly handles all LOGICAL_TYPES — including elemMatch with its attribute — addressing the concerns raised in prior review rounds. The PR also bundles an unrelated docblock narrowing in groupByType() (array<static>list<self>) that could affect static analysis for subclasses.

Confidence Score: 4/5

Safe to merge after addressing the docblock annotation regression in groupByType(); the fingerprint logic itself is correct.

The fingerprint implementation is solid — all LOGICAL_TYPES are recursively handled, elemMatch attribute is correctly included, type validation is in place, and tests are comprehensive. The only real concern is the unrelated list docblock change that loses late-static-binding information, which could break static analysis for subclass users of this library.

The groupByType() return type docblock in src/Query/Query.php (lines 887-898) needs attention due to the array → list narrowing.

Important Files Changed

Filename Overview
src/Query/Query.php Adds fingerprint() + private queryShape() with correct recursive handling of all LOGICAL_TYPES including elemMatch; also bundles an unrelated docblock narrowing (array → list) in groupByType()
tests/Query/QueryTest.php Comprehensive tests for fingerprint(): covers same-shape/different-values, attribute diff, method diff, order-independence, parsed objects, empty array, nested logical queries (and/or/elemMatch), and invalid input rejection

Reviews (4): Last reviewed commit: "fix: include elemMatch attribute in fing..." | Re-trigger Greptile

Comment thread src/Query/Query.php Outdated
Comment thread src/Query/Query.php Outdated
Comment thread src/Query/Query.php Outdated
Previously all `and(...)`, `or(...)`, and `elemMatch(...)` queries hashed as `and:` / `or:` / `elemMatch:` regardless of their child shapes, defeating the purpose of fingerprinting for slow-query pattern grouping.

The helper now recurses into logical query children, producing canonical shapes like `and(equal:name|greaterThan:age)`. Invalid array elements (non-string, non-Query) throw a QueryException instead of a fatal PHP error.

Added tests for nested AND/OR differentiation, child-order independence, and rejection of invalid elements.
@lohanidamodar lohanidamodar force-pushed the CLO-4204-query-fingerprint branch from dc644d1 to bd6af2e Compare April 20, 2026 05:09
Comment thread src/Query/Query.php Outdated
Logical queries and/or have an empty attribute, but elemMatch carries the field name being matched. Without including the attribute, elemMatch('tags', [...]) and elemMatch('categories', [...]) with the same inner shape would hash identically.

Canonical shape is now `method:attribute(child1|child2)` for logical types — and/or are unaffected (attribute empty), elemMatch preserves the field.
@abnegate abnegate merged commit 2b7dfd7 into main Apr 20, 2026
4 checks passed
@abnegate abnegate deleted the CLO-4204-query-fingerprint branch April 20, 2026 05: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.

2 participants