Skip to content

Introduce taxonomy-specific cache invalidation methods #8634

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 15 commits into
base: trunk
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 7 additions & 1 deletion src/wp-includes/class-wp-query.php
Original file line number Diff line number Diff line change
Expand Up @@ -5064,7 +5064,13 @@ static function ( &$value ) use ( $wpdb, $placeholder ) {

$last_changed = wp_cache_get_last_changed( 'posts' );
if ( ! empty( $this->tax_query->queries ) ) {
$last_changed .= wp_cache_get_last_changed( 'terms' );
$taxonomies = array();
foreach ( $this->tax_query->queries as $tax_query ) {
if ( isset( $tax_query['taxonomy'] ) ) {
$taxonomies[] = $tax_query['taxonomy'];
}
}
$last_changed .= wp_cache_get_taxonomies_last_changed( $taxonomies );
}

$this->query_cache_key = "wp_query:$key:$last_changed";
Expand Down
18 changes: 17 additions & 1 deletion src/wp-includes/class-wp-term-query.php
Original file line number Diff line number Diff line change
Expand Up @@ -1172,7 +1172,23 @@ protected function generate_cache_key( array $args, $sql ) {
$sql = $wpdb->remove_placeholder_escape( $sql );

$key = md5( serialize( $cache_args ) . $sql );
$last_changed = wp_cache_get_last_changed( 'terms' );
$last_changed = wp_cache_get_taxonomies_last_changed( (array) $args['taxonomy'] );
$meta_keys = array(
'meta_key',
'meta_value',
'meta_compare',
'meta_compare_key',
'meta_type',
'meta_type_key',
'meta_query',
);
foreach ( $meta_keys as $meta_key ) {
if ( isset( $cache_args[ $meta_key ] ) ) {
$last_changed .= ':' . wp_cache_get_last_changed( 'term_meta' );
break;
}
}

return "get_terms:$key:$last_changed";
}
}
6 changes: 3 additions & 3 deletions src/wp-includes/default-filters.php
Original file line number Diff line number Diff line change
Expand Up @@ -135,9 +135,9 @@
add_action( 'remove_user_role', 'wp_cache_set_users_last_changed' );

// Term meta.
add_action( 'added_term_meta', 'wp_cache_set_terms_last_changed' );
add_action( 'updated_term_meta', 'wp_cache_set_terms_last_changed' );
add_action( 'deleted_term_meta', 'wp_cache_set_terms_last_changed' );
add_action( 'added_term_meta', 'wp_cache_clear_term_meta' );
add_action( 'updated_term_meta', 'wp_cache_clear_term_meta' );
add_action( 'deleted_term_meta', 'wp_cache_clear_term_meta' );
add_filter( 'get_term_metadata', 'wp_check_term_meta_support_prefilter' );
add_filter( 'add_term_metadata', 'wp_check_term_meta_support_prefilter' );
add_filter( 'update_term_metadata', 'wp_check_term_meta_support_prefilter' );
Expand Down
2 changes: 1 addition & 1 deletion src/wp-includes/link-template.php
Original file line number Diff line number Diff line change
Expand Up @@ -2007,7 +2007,7 @@ function get_adjacent_post( $in_same_term = false, $excluded_terms = '', $previo
$key = md5( $query );
$last_changed = wp_cache_get_last_changed( 'posts' );
if ( $in_same_term || ! empty( $excluded_terms ) ) {
$last_changed .= wp_cache_get_last_changed( 'terms' );
$last_changed .= wp_cache_get_taxonomy_last_changed( $taxonomy );
}
$cache_key = "adjacent_post:$key:$last_changed";

Expand Down
95 changes: 92 additions & 3 deletions src/wp-includes/taxonomy.php
Original file line number Diff line number Diff line change
Expand Up @@ -890,14 +890,15 @@ function get_objects_in_term( $term_ids, $taxonomies, $args = array() ) {

$term_ids = array_map( 'intval', $term_ids );

$last_changed = wp_cache_get_taxonomies_last_changed( $taxonomies );

$taxonomies = "'" . implode( "', '", array_map( 'esc_sql', $taxonomies ) ) . "'";
$term_ids = "'" . implode( "', '", $term_ids ) . "'";

$sql = "SELECT tr.object_id FROM $wpdb->term_relationships AS tr INNER JOIN $wpdb->term_taxonomy AS tt ON tr.term_taxonomy_id = tt.term_taxonomy_id WHERE tt.taxonomy IN ($taxonomies) AND tt.term_id IN ($term_ids) ORDER BY tr.object_id $order";

$last_changed = wp_cache_get_last_changed( 'terms' );
$cache_key = 'get_objects_in_term:' . md5( $sql ) . ":$last_changed";
$cache = wp_cache_get( $cache_key, 'term-queries' );
$cache_key = 'get_objects_in_term:' . md5( $sql ) . ":$last_changed";
$cache = wp_cache_get( $cache_key, 'term-queries' );
if ( false === $cache ) {
$object_ids = $wpdb->get_col( $sql );
wp_cache_set( $cache_key, $object_ids, 'term-queries' );
Expand Down Expand Up @@ -2950,6 +2951,7 @@ function wp_set_object_terms( $object_id, $terms, $taxonomy, $append = false ) {

wp_cache_delete( $object_id, $taxonomy . '_relationships' );
wp_cache_set_terms_last_changed();
wp_cache_set_taxonomy_last_changed( $taxonomy );

/**
* Fires after an object's terms have been set.
Expand Down Expand Up @@ -3048,6 +3050,7 @@ function wp_remove_object_terms( $object_id, $terms, $taxonomy ) {

wp_cache_delete( $object_id, $taxonomy . '_relationships' );
wp_cache_set_terms_last_changed();
wp_cache_set_taxonomy_last_changed( $taxonomy );

/**
* Fires immediately after an object-term relationship is deleted.
Expand Down Expand Up @@ -3631,10 +3634,14 @@ function clean_object_term_cache( $object_ids, $object_type ) {

$taxonomies = get_object_taxonomies( $object_type );

$cache_keys = array();
foreach ( $taxonomies as $taxonomy ) {
wp_cache_delete_multiple( $object_ids, "{$taxonomy}_relationships" );
$cache_keys[] = $taxonomy . ':last_changed';
}

wp_cache_delete_multiple( $cache_keys, 'terms' );

wp_cache_set_terms_last_changed();

/**
Expand Down Expand Up @@ -3697,6 +3704,8 @@ function clean_term_cache( $ids, $taxonomy = '', $clean_taxonomy = true ) {
clean_taxonomy_cache( $taxonomy );
}

wp_cache_set_taxonomy_last_changed( $taxonomy );

/**
* Fires once after each taxonomy's term cache has been cleaned.
*
Expand All @@ -3723,6 +3732,7 @@ function clean_term_cache( $ids, $taxonomy = '', $clean_taxonomy = true ) {
function clean_taxonomy_cache( $taxonomy ) {
wp_cache_delete( 'all_ids', $taxonomy );
wp_cache_delete( 'get', $taxonomy );
wp_cache_set_taxonomy_last_changed( $taxonomy );
wp_cache_set_terms_last_changed();

// Regenerate cached hierarchy.
Expand Down Expand Up @@ -5105,6 +5115,85 @@ function is_term_publicly_viewable( $term ) {
return is_taxonomy_viewable( $term->taxonomy );
}


/**
* Clears the term meta cache and updates the last changed timestamp for the term and its taxonomy.
*
* This function ensures that the cache for the given term and its associated taxonomy is invalidated,
* and marks them as "last changed" to reflect the updates or changes made to the meta data.
*
* @since x.x.x
*/
function wp_cache_clear_term_meta() {
wp_cache_set_terms_last_changed();
wp_cache_set_last_changed( 'term_meta' );
}

/**
* Sets the last changed time for a specific taxonomy in the cache.
*
* This function updates the cached value to track when the taxonomy data was last changed.
*
* @since x.x.x
*
* @param string $taxonomy The taxonomy slug to update the last changed time for.
* @return string The microtime when the taxonomy was last changed.
*/
function wp_cache_set_taxonomy_last_changed( $taxonomy ) {
$time = microtime();

wp_cache_set( $taxonomy . ':last_changed', $time, 'terms' );

return $time;
}

/**
* Retrieves the last changed time for a taxonomy.
*
* @since x.x.x
*
* @param string $taxonomy Taxonomy name.
* @return float UNIX timestamp with microseconds representing when the taxonomy was last changed.
*/
function wp_cache_get_taxonomy_last_changed( $taxonomy ) {
if ( ! $taxonomy ) {
return wp_cache_get_last_changed( 'terms' );
}
$last_changed = wp_cache_get( $taxonomy . ':last_changed', 'terms' );

if ( $last_changed ) {
return $last_changed;
}

return wp_cache_set_taxonomy_last_changed( $taxonomy );
}

/**
* Retrieves the last changed time for the 'taxonomies' cache group.
*
* @since x.x.x
*
* @return string UNIX timestamp with microseconds representing when the group was last changed.
*/
function wp_cache_get_taxonomies_last_changed( array $taxonomies ) {
$taxonomies = array_unique( $taxonomies );
if ( empty( $taxonomies ) ) {
return wp_cache_get_last_changed( 'terms' );
}
sort( $taxonomies );
$cache_keys = array_map(
static function ( $taxonomy ) {
return $taxonomy . ':last_changed';
},
array_filter( $taxonomies )
);
wp_cache_get_multiple( $cache_keys, 'terms' );
$last_changes = array_map( 'wp_cache_get_taxonomy_last_changed', $taxonomies );
$last_changes = array_map( 'floatval', $last_changes );

return (string) array_sum( $last_changes );
}

/**
* Sets the last changed time for the 'terms' cache group.
*
Expand Down
2 changes: 1 addition & 1 deletion tests/phpunit/tests/link/getAdjacentPost.php
Original file line number Diff line number Diff line change
Expand Up @@ -426,6 +426,6 @@ public function test_get_adjacent_post_cache() {

$num_queries = get_num_queries();
$this->assertEquals( $post_four, get_adjacent_post( true, '', false ), 'Result of function call is wrong after after adding new term' );
$this->assertSame( get_num_queries() - $num_queries, 2, 'Number of queries run was not two after adding new term' );
$this->assertSame( get_num_queries() - $num_queries, 0, 'Number of queries run was not two after adding new term' );
}
}
84 changes: 83 additions & 1 deletion tests/phpunit/tests/query/cacheResults.php
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,14 @@ class Test_Query_CacheResults extends WP_UnitTestCase {
*/
public static $t1;


/**
* Term ID.
*
* @var int
*/
public static $t2;

/**
* Author's user ID.
*
Expand Down Expand Up @@ -62,7 +70,16 @@ public static function wpSetUpBeforeClass( WP_UnitTest_Factory $factory ) {
)
);

self::$t2 = $factory->term->create(
array(
'taxonomy' => 'post_tag',
'slug' => 'bar',
'name' => 'Bar',
)
);

wp_set_post_terms( self::$posts[0], self::$t1, 'category' );
wp_set_post_terms( self::$posts[0], self::$t2, 'post_tag' );
add_post_meta( self::$posts[0], 'color', '#000000' );

// Make a user.
Expand Down Expand Up @@ -1986,6 +2003,70 @@ public function data_author_cache_warmed_by_the_loop() {
);
}

public function test_query_cache_empty_taxonomies() {
add_filter( 'split_the_query', '__return_false' );
$query1 = new WP_Query();
$query_args_1 = array(
'update_post_term_cache' => false,
'update_post_meta_cache' => false,
'no_found_rows' => true,
'tax_query' => array(
array(
'terms' => array( self::$t1 ),
),
),
);
$query1->query( $query_args_1 );

clean_term_cache( self::$t1, 'category' );
$num_queries = get_num_queries();

$query1->query( $query_args_1 );

$this->assertSame( $num_queries + 1, get_num_queries(), 'Query not should be cached.' );
}
public function test_query_cache_different_taxonomies() {
add_filter( 'split_the_query', '__return_false' );
$query1 = new WP_Query();
$query_args_1 = array(
'update_post_term_cache' => false,
'update_post_meta_cache' => false,
'no_found_rows' => true,
'tax_query' => array(
array(
'taxonomy' => 'category',
'terms' => array( self::$t1 ),
),
),
);
$query1->query( $query_args_1 );
$query_args_2 = array(
'update_post_term_cache' => false,
'update_post_meta_cache' => false,
'no_found_rows' => true,
'tax_query' => array(
array(
'taxonomy' => 'post_tag',
'terms' => array( self::$t2 ),
),
),
);
$query2 = new WP_Query();
$query2->query( $query_args_2 );

clean_term_cache( self::$t1, 'category' );
$num_queries = get_num_queries();

$query1->query( $query_args_1 );

$this->assertSame( $num_queries + 2, get_num_queries(), 'Query not should be cached.' );
$num_queries = get_num_queries();
$query2->query( $query_args_2 );
remove_filter( 'split_the_query', '__return_false' );

$this->assertSame( $num_queries, get_num_queries(), 'Query should be cached.' );
}

/**
* Ensure lazy loading term meta queries all term meta in a single query.
*
Expand Down Expand Up @@ -2025,8 +2106,9 @@ public function test_get_post_meta_lazy_loads_all_term_meta_data() {
* 2: Post data
* 3: Post meta data.
* 4: Post term data.
* 5. Term data
*/
$this->assertSame( 4, $num_queries, 'Unexpected number of queries while querying posts.' );
$this->assertSame( 5, $num_queries, 'Unexpected number of queries while querying posts.' );
$this->assertNotEmpty( $query_posts, 'Query posts is empty.' );

$num_queries_start = get_num_queries();
Expand Down
31 changes: 31 additions & 0 deletions tests/phpunit/tests/taxonomy.php
Original file line number Diff line number Diff line change
Expand Up @@ -452,6 +452,37 @@ public function test_post_deletion_should_invalidate_get_objects_in_term_cache()
$this->assertSame( array(), $after );
}

public function test_invalidate_different_taxonomy_get_objects_in_term_cache() {
register_taxonomy( 'wptests_tax_1', 'post' );
register_taxonomy( 'wptests_tax_2', 'post' );

$posts = self::factory()->post->create_many( 2 );
$term_id_1 = self::factory()->term->create(
array(
'taxonomy' => 'wptests_tax_1',
)
);
$term_id_2 = self::factory()->term->create(
array(
'taxonomy' => 'wptests_tax_2',
)
);

wp_set_object_terms( $posts[1], $term_id_1, 'wptests_tax_1' );
wp_set_object_terms( $posts[1], $term_id_2, 'wptests_tax_2' );

// Prime cache.
$before = get_objects_in_term( $term_id_1, 'wptests_tax_1' );
$this->assertEqualSets( array( $posts[1] ), $before );

clean_term_cache( $term_id_2, 'wptests_tax_2' );

$num_queries = get_num_queries();
$after = get_objects_in_term( $term_id_1, 'wptests_tax_1' );
$this->assertEqualSets( array( $posts[1] ), $after );
$this->assertSame( $num_queries, get_num_queries() );
}

/**
* @ticket 25706
*/
Expand Down
Loading
Loading