feat: add Query::fingerprint() for shape-only query hashing#3
Conversation
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 SummaryAdds Confidence Score: 4/5Safe 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
Reviews (4): Last reviewed commit: "fix: include elemMatch attribute in fing..." | Re-trigger Greptile |
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.
dc644d1 to
bd6af2e
Compare
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.
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")andequal("name", "Bob")produce the same fingerprint.Behavior
Queryobjectsmethod:attribute|method:attribute|...stringmd5(''))Test plan
Added
testFingerprint()intests/Query/QueryTest.phpcovering:QueryobjectsAll 19 tests pass locally (45 assertions).