Taxonomy: Only store term_ids and object_ids in `WP_Term_Query` query caches.

The query cache currently implemented in `WP_Term_Query` caches the final output of the query, depending on what fields are requested. This is wasteful, as if a user requests `fields` => `all`, then an unlimited array of `WP_Term` objects could be stored in the object cache. Instead of storing the whole WP_Term object, this change only the term_id is stored. To get an array the full WP_Term objects, the `_prime_term_caches` function is called with an array of ids. In instances where a persistent object cache is not in use, then this will result in another SQL query to be run. After `_prime_term_caches` is called if this term is requested again in the same page load, then it will already be loaded into memory.  If a user runs `WP_Term_Query` with the fields param set to `all_with_object_id`, an array of objects containing both the term_id and object_ids are stored in cache. 

This change also improves the logic to load term meta caches. This change ensures that term meta is always primed for all terms loaded in the term query. 

Props Spacedmonkey, boonebgorges, jbpaul17, peterwilsoncc, flixos90, pbearne.
Fixes #37189.

Built from https://develop.svn.wordpress.org/trunk@52836


git-svn-id: http://core.svn.wordpress.org/trunk@52425 1a063a9b-81f0-0310-95a4-ce76da25c4cd
This commit is contained in:
spacedmonkey 2022-03-10 10:58:09 +00:00
parent fa364267d4
commit 8771a658ad
2 changed files with 103 additions and 94 deletions

View File

@ -636,32 +636,16 @@ class WP_Term_Query {
$selects = array();
switch ( $args['fields'] ) {
case 'all':
case 'all_with_object_id':
case 'tt_ids':
case 'slugs':
$selects = array( 't.*', 'tt.*' );
if ( 'all_with_object_id' === $args['fields'] && ! empty( $args['object_ids'] ) ) {
$selects[] = 'tr.object_id';
}
break;
case 'ids':
case 'id=>parent':
$selects = array( 't.term_id', 'tt.parent', 'tt.count', 'tt.taxonomy' );
break;
case 'names':
$selects = array( 't.term_id', 'tt.parent', 'tt.count', 't.name', 'tt.taxonomy' );
break;
case 'count':
$orderby = '';
$order = '';
$selects = array( 'COUNT(*)' );
break;
case 'id=>name':
$selects = array( 't.term_id', 't.name', 'tt.parent', 'tt.count', 'tt.taxonomy' );
break;
case 'id=>slug':
$selects = array( 't.term_id', 't.slug', 'tt.parent', 'tt.count', 'tt.taxonomy' );
default:
$selects = array( 't.term_id' );
if ( 'all_with_object_id' === $args['fields'] && ! empty( $args['object_ids'] ) ) {
$selects[] = 'tr.object_id';
}
break;
}
@ -688,7 +672,8 @@ class WP_Term_Query {
$join .= " INNER JOIN $wpdb->term_taxonomy AS tt ON t.term_id = tt.term_id";
if ( ! empty( $this->query_vars['object_ids'] ) ) {
$join .= " INNER JOIN {$wpdb->term_relationships} AS tr ON tr.term_taxonomy_id = tt.term_taxonomy_id";
$join .= " INNER JOIN {$wpdb->term_relationships} AS tr ON tr.term_taxonomy_id = tt.term_taxonomy_id";
$distinct = 'DISTINCT';
}
$where = implode( ' AND ', $this->sql_clauses['where'] );
@ -748,8 +733,14 @@ class WP_Term_Query {
$cache_key = "get_terms:$key:$last_changed";
$cache = wp_cache_get( $cache_key, 'terms' );
if ( false !== $cache ) {
if ( 'all' === $_fields || 'all_with_object_id' === $_fields ) {
$cache = $this->populate_terms( $cache );
if ( 'ids' === $_fields ) {
$term_ids = wp_list_pluck( $cache, 'term_id' );
$cache = array_map( 'intval', $term_ids );
} elseif ( 'count' !== $_fields ) {
$term_ids = wp_list_pluck( $cache, 'term_id' );
_prime_term_caches( $term_ids, $args['update_term_meta_cache'] );
$term_objects = $this->populate_terms( $cache );
$cache = $this->format_terms( $term_objects, $_fields );
}
$this->terms = $cache;
@ -757,33 +748,27 @@ class WP_Term_Query {
}
if ( 'count' === $_fields ) {
$count = $wpdb->get_var( $this->request );
$count = $wpdb->get_var( $this->request ); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared
wp_cache_set( $cache_key, $count, 'terms' );
return $count;
}
$terms = $wpdb->get_results( $this->request );
if ( 'all' === $_fields || 'all_with_object_id' === $_fields ) {
update_term_cache( $terms );
}
// Prime termmeta cache.
if ( $args['update_term_meta_cache'] ) {
$term_ids = wp_list_pluck( $terms, 'term_id' );
update_termmeta_cache( $term_ids );
}
$terms = $wpdb->get_results( $this->request ); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared
if ( empty( $terms ) ) {
wp_cache_add( $cache_key, array(), 'terms' );
return array();
}
$term_ids = wp_list_pluck( $terms, 'term_id' );
_prime_term_caches( $term_ids, false );
$term_objects = $this->populate_terms( $terms );
if ( $child_of ) {
foreach ( $taxonomies as $_tax ) {
$children = _get_term_hierarchy( $_tax );
if ( ! empty( $children ) ) {
$terms = _get_term_children( $child_of, $terms, $_tax );
$term_objects = _get_term_children( $child_of, $term_objects, $_tax );
}
}
}
@ -791,13 +776,13 @@ class WP_Term_Query {
// Update term counts to include children.
if ( $args['pad_counts'] && 'all' === $_fields ) {
foreach ( $taxonomies as $_tax ) {
_pad_term_counts( $terms, $_tax );
_pad_term_counts( $term_objects, $_tax );
}
}
// Make sure we show empty categories that have children.
if ( $hierarchical && $args['hide_empty'] && is_array( $terms ) ) {
foreach ( $terms as $k => $term ) {
if ( $hierarchical && $args['hide_empty'] && is_array( $term_objects ) ) {
foreach ( $term_objects as $k => $term ) {
if ( ! $term->count ) {
$children = get_term_children( $term->term_id, $term->taxonomy );
if ( is_array( $children ) ) {
@ -810,7 +795,7 @@ class WP_Term_Query {
}
// It really is empty.
unset( $terms[ $k ] );
unset( $term_objects[ $k ] );
}
}
}
@ -836,56 +821,26 @@ class WP_Term_Query {
$terms = $_terms;
}
$_terms = array();
if ( 'id=>parent' === $_fields ) {
foreach ( $terms as $term ) {
$_terms[ $term->term_id ] = $term->parent;
}
} elseif ( 'ids' === $_fields ) {
foreach ( $terms as $term ) {
$_terms[] = (int) $term->term_id;
}
} elseif ( 'tt_ids' === $_fields ) {
foreach ( $terms as $term ) {
$_terms[] = (int) $term->term_taxonomy_id;
}
} elseif ( 'names' === $_fields ) {
foreach ( $terms as $term ) {
$_terms[] = $term->name;
}
} elseif ( 'slugs' === $_fields ) {
foreach ( $terms as $term ) {
$_terms[] = $term->slug;
}
} elseif ( 'id=>name' === $_fields ) {
foreach ( $terms as $term ) {
$_terms[ $term->term_id ] = $term->name;
}
} elseif ( 'id=>slug' === $_fields ) {
foreach ( $terms as $term ) {
$_terms[ $term->term_id ] = $term->slug;
}
}
if ( ! empty( $_terms ) ) {
$terms = $_terms;
}
// Hierarchical queries are not limited, so 'offset' and 'number' must be handled now.
if ( $hierarchical && $number && is_array( $terms ) ) {
if ( $offset >= count( $terms ) ) {
$terms = array();
$terms = array();
$term_objects = array();
} else {
$terms = array_slice( $terms, $offset, $number, true );
$terms = array_slice( $terms, $offset, $number, true );
$term_objects = array_slice( $term_objects, $offset, $number, true );
}
}
wp_cache_add( $cache_key, $terms, 'terms' );
if ( 'all' === $_fields || 'all_with_object_id' === $_fields ) {
$terms = $this->populate_terms( $terms );
// Prime termmeta cache.
if ( $args['update_term_meta_cache'] ) {
$term_ids = wp_list_pluck( $term_objects, 'term_id' );
update_termmeta_cache( $term_ids );
}
wp_cache_add( $cache_key, $terms, 'terms' );
$terms = $this->format_terms( $term_objects, $_fields );
$this->terms = $terms;
return $this->terms;
}
@ -949,6 +904,53 @@ class WP_Term_Query {
return $orderby;
}
/**
* Format response depending on field requested.
*
* @since 6.0.0
*
* @param WP_Term[] $term_objects Array of term objects.
* @param string $_fields Field to format.
*
* @return WP_Term[]|int[]|string[] Array of terms / strings / ints depending on field requested.
*/
protected function format_terms( $term_objects, $_fields ) {
$_terms = array();
if ( 'id=>parent' === $_fields ) {
foreach ( $term_objects as $term ) {
$_terms[ $term->term_id ] = $term->parent;
}
} elseif ( 'ids' === $_fields ) {
foreach ( $term_objects as $term ) {
$_terms[] = (int) $term->term_id;
}
} elseif ( 'tt_ids' === $_fields ) {
foreach ( $term_objects as $term ) {
$_terms[] = (int) $term->term_taxonomy_id;
}
} elseif ( 'names' === $_fields ) {
foreach ( $term_objects as $term ) {
$_terms[] = $term->name;
}
} elseif ( 'slugs' === $_fields ) {
foreach ( $term_objects as $term ) {
$_terms[] = $term->slug;
}
} elseif ( 'id=>name' === $_fields ) {
foreach ( $term_objects as $term ) {
$_terms[ $term->term_id ] = $term->name;
}
} elseif ( 'id=>slug' === $_fields ) {
foreach ( $term_objects as $term ) {
$_terms[ $term->term_id ] = $term->slug;
}
} elseif ( 'all' === $_fields || 'all_with_object_id' === $_fields ) {
$_terms = $term_objects;
}
return $_terms;
}
/**
* Generate the ORDER BY clause for an 'orderby' param that is potentially related to a meta query.
*
@ -1053,23 +1055,30 @@ class WP_Term_Query {
*
* @since 4.9.8
*
* @param array $term_ids Term IDs.
* @return array
* @param Object[]|int[] $terms List of objects or term ids.
* @return WP_Term[] Array of `WP_Term` objects.
*/
protected function populate_terms( $term_ids ) {
$terms = array();
if ( ! is_array( $term_ids ) ) {
return $terms;
protected function populate_terms( $terms ) {
$term_objects = array();
if ( ! is_array( $terms ) ) {
return $term_objects;
}
foreach ( $term_ids as $key => $term_id ) {
$term = get_term( $term_id );
foreach ( $terms as $key => $term_data ) {
if ( is_object( $term_data ) && property_exists( $term_data, 'term_id' ) ) {
$term = get_term( $term_data->term_id );
if ( property_exists( $term_data, 'object_id' ) ) {
$term->object_id = (int) $term_data->object_id;
}
} else {
$term = get_term( $term_data );
}
if ( $term instanceof WP_Term ) {
$terms[ $key ] = $term;
$term_objects[ $key ] = $term;
}
}
return $terms;
return $term_objects;
}
}

View File

@ -16,7 +16,7 @@
*
* @global string $wp_version
*/
$wp_version = '6.0-alpha-52835';
$wp_version = '6.0-alpha-52836';
/**
* Holds the WordPress DB revision, increments when changes are made to the WordPress DB schema.