Skip to content

Query: key the result cache on what the query-clauses filter changed. - #276

Open
robincornett wants to merge 1 commit into
berlindb:masterfrom
robincornett:issue/275
Open

robincornett wants to merge 1 commit into
berlindb:masterfrom
robincornett:issue/275

Conversation

@robincornett

Copy link
Copy Markdown
Contributor

Summary

Fixes #275 by keying the result cache on whatever the {prefix}_{items}_query_clauses filter changed, in addition to the query vars.

  • filter_query_clauses() records which clauses its callbacks changed (a removed clause as null), and get_cache_key() hashes that alongside the existing query-var slice. A query no callback changes hashes exactly what it did before, so RESULTS_INVARIANT_VARS and the index_hints contract in CacheKeyGuardTest are untouched.
  • get_items() builds the request before the cache lookup (new set_item_ids_request()), so the filter has run when the key is computed. get_item_ids() now only executes it.
  • run_aggregate() is split into get_aggregate_request() (build) and run_aggregate() (execute and shape), so aggregate queries get the same treatment.
  • Covers a callback reading an unregistered query var (the [Bug]: Result cache ignores query vars read only by a query_clauses filter, so differently filtered queries share one entry #275 reproduction, in both orders), a callback reading state outside the query vars, and the aggregate path, plus two CacheKeyGuardTest cases: a var only a clauses callback reads must segment the key, and a hooked callback that changes nothing must not.

Testing

  • composer validate --strict --no-check-publish
  • vendor/bin/phpstan analyse --memory-limit=1G
  • vendor/bin/phpcs
  • bin/run-tests.sh -p 8.1 -w 7.1 -- --group default
  • bin/run-tests.sh -p 8.2 -w 7.1 -- --group default

The new QueryCacheTest and CacheKeyGuardTest cases fail on master (a22276b): each filtered query gets the other query's cached rows, and the filtered aggregate returns the unfiltered count.

Notes

Why not a key built from the compiled SQL (the second option in #275). index_hints changes the SQL without changing the rows, and CacheKeyGuardTest::test_index_hints_does_not_segment_cache_key() requires the two to share an entry, so an SQL key would break that by design. It would also need $wpdb->remove_placeholder_escape(), which Interfaces\Connection doesn't expose. Keying on the filter's changes covers the same cases, because the clauses the filter receives are built from vars the parsers register, which the existing slice already hashes.

Why not hash every query var (the first option). It misses callbacks that read state outside the query vars, such as a list-table filter reading $_GET, which no var registration can fix.

Behavior changes to review.

  • Clauses filters now run on cache hits as well as misses. A hit still runs no query, but a callback with side effects now fires on every call.
  • The placeholder escape isn't stripped, so a filtered query whose changed clause contains a prepared literal % misses the cache across requests. Results stay correct; only that narrow case loses cross-request caching.
  • prime_query() doesn't run the filter, so its primed entries only match unfiltered queries. A filtered query misses rather than reading primed IDs its filter never saw.

Backports. #275 also affects 3.0.0 and 2.0.3. I've left release/3.0.x and release/2.0.x alone pending your call on whether to backport.

A {prefix}_{items}_query_clauses callback can narrow the rows on a query var with no registered default, or on state outside the query vars, and neither reached the cache key, so differently filtered queries shared one entry. Build the request before the cache lookup, record which clauses the filter changed, and hash those alongside the query vars. A query no callback changes keeps its previous key, so results-invariant vars still share one entry.

Fixes berlindb#275.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
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.

[Bug]: Result cache ignores query vars read only by a query_clauses filter, so differently filtered queries share one entry

1 participant