From 8771a658ad081674e7d883679214e5e3cda7f935 Mon Sep 17 00:00:00 2001 From: spacedmonkey Date: Thu, 10 Mar 2022 10:58:09 +0000 Subject: [PATCH] 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 --- wp-includes/class-wp-term-query.php | 195 +++++++++++++++------------- wp-includes/version.php | 2 +- 2 files changed, 103 insertions(+), 94 deletions(-) diff --git a/wp-includes/class-wp-term-query.php b/wp-includes/class-wp-term-query.php index 1583ed6ab6..81a5fcfa89 100644 --- a/wp-includes/class-wp-term-query.php +++ b/wp-includes/class-wp-term-query.php @@ -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; } } diff --git a/wp-includes/version.php b/wp-includes/version.php index 2fd16f4036..7705c0ca83 100644 --- a/wp-includes/version.php +++ b/wp-includes/version.php @@ -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.