Make WordPress Core

Changeset 52836


Ignore:
Timestamp:
03/10/2022 10:56:09 AM (5 years ago)
Author:
spacedmonkey
Message:

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.

Location:
trunk
Files:
5 edited

Legend:

Unmodified
Added
Removed
  • trunk/src/wp-includes/class-wp-term-query.php

    r52669 r52836  
    637637                $selects = array();
    638638                switch ( $args['fields'] ) {
    639                         case 'all':
    640                         case 'all_with_object_id':
    641                         case 'tt_ids':
    642                         case 'slugs':
    643                                 $selects = array( 't.*', 'tt.*' );
    644                                 if ( 'all_with_object_id' === $args['fields'] && ! empty( $args['object_ids'] ) ) {
    645                                         $selects[] = 'tr.object_id';
    646                                 }
    647                                 break;
    648                         case 'ids':
    649                         case 'id=>parent':
    650                                 $selects = array( 't.term_id', 'tt.parent', 'tt.count', 'tt.taxonomy' );
    651                                 break;
    652                         case 'names':
    653                                 $selects = array( 't.term_id', 'tt.parent', 'tt.count', 't.name', 'tt.taxonomy' );
    654                                 break;
    655639                        case 'count':
    656640                                $orderby = '';
    … …  
    658642                                $selects = array( 'COUNT(*)' );
    659643                                break;
    660                         case 'id=>name':
    661                                 $selects = array( 't.term_id', 't.name', 'tt.parent', 'tt.count', 'tt.taxonomy' );
    662                                 break;
    663                         case 'id=>slug':
    664                                 $selects = array( 't.term_id', 't.slug', 'tt.parent', 'tt.count', 'tt.taxonomy' );
     644                        default:
     645                                $selects = array( 't.term_id' );
     646                                if ( 'all_with_object_id' === $args['fields'] && ! empty( $args['object_ids'] ) ) {
     647                                        $selects[] = 'tr.object_id';
     648                                }
    665649                                break;
    666650                }
    … …  
    689673
    690674                if ( ! empty( $this->query_vars['object_ids'] ) ) {
    691                         $join .= " INNER JOIN {$wpdb->term_relationships} AS tr ON tr.term_taxonomy_id = tt.term_taxonomy_id";
     675                        $join    .= " INNER JOIN {$wpdb->term_relationships} AS tr ON tr.term_taxonomy_id = tt.term_taxonomy_id";
     676                        $distinct = 'DISTINCT';
    692677                }
    693678
    … …  
    749734                $cache        = wp_cache_get( $cache_key, 'terms' );
    750735                if ( false !== $cache ) {
    751                         if ( 'all' === $_fields || 'all_with_object_id' === $_fields ) {
    752                                 $cache = $this->populate_terms( $cache );
     736                        if ( 'ids' === $_fields ) {
     737                                $term_ids = wp_list_pluck( $cache, 'term_id' );
     738                                $cache    = array_map( 'intval', $term_ids );
     739                        } elseif ( 'count' !== $_fields ) {
     740                                $term_ids = wp_list_pluck( $cache, 'term_id' );
     741                                _prime_term_caches( $term_ids, $args['update_term_meta_cache'] );
     742                                $term_objects = $this->populate_terms( $cache );
     743                                $cache        = $this->format_terms( $term_objects, $_fields );
    753744                        }
    754745
    … …  
    758749
    759750                if ( 'count' === $_fields ) {
    760                         $count = $wpdb->get_var( $this->request );
     751                        $count = $wpdb->get_var( $this->request ); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared
    761752                        wp_cache_set( $cache_key, $count, 'terms' );
    762753                        return $count;
    763754                }
    764755
    765                 $terms = $wpdb->get_results( $this->request );
    766 
    767                 if ( 'all' === $_fields || 'all_with_object_id' === $_fields ) {
    768                         update_term_cache( $terms );
    769                 }
    770 
    771                 // Prime termmeta cache.
    772                 if ( $args['update_term_meta_cache'] ) {
    773                         $term_ids = wp_list_pluck( $terms, 'term_id' );
    774                         update_termmeta_cache( $term_ids );
    775                 }
     756                $terms = $wpdb->get_results( $this->request ); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared
    776757
    777758                if ( empty( $terms ) ) {
    … …  
    779760                        return array();
    780761                }
     762
     763                $term_ids = wp_list_pluck( $terms, 'term_id' );
     764                _prime_term_caches( $term_ids, false );
     765                $term_objects = $this->populate_terms( $terms );
    781766
    782767                if ( $child_of ) {
    … …  
    784769                                $children = _get_term_hierarchy( $_tax );
    785770                                if ( ! empty( $children ) ) {
    786                                         $terms = _get_term_children( $child_of, $terms, $_tax );
     771                                        $term_objects = _get_term_children( $child_of, $term_objects, $_tax );
    787772                                }
    788773                        }
    … …  
    792777                if ( $args['pad_counts'] && 'all' === $_fields ) {
    793778                        foreach ( $taxonomies as $_tax ) {
    794                                 _pad_term_counts( $terms, $_tax );
     779                                _pad_term_counts( $term_objects, $_tax );
    795780                        }
    796781                }
    797782
    798783                // Make sure we show empty categories that have children.
    799                 if ( $hierarchical && $args['hide_empty'] && is_array( $terms ) ) {
    800                         foreach ( $terms as $k => $term ) {
     784                if ( $hierarchical && $args['hide_empty'] && is_array( $term_objects ) ) {
     785                        foreach ( $term_objects as $k => $term ) {
    801786                                if ( ! $term->count ) {
    802787                                        $children = get_term_children( $term->term_id, $term->taxonomy );
    … …  
    811796
    812797                                        // It really is empty.
    813                                         unset( $terms[ $k ] );
     798                                        unset( $term_objects[ $k ] );
    814799                                }
    815800                        }
    … …  
    837822                }
    838823
    839                 $_terms = array();
    840                 if ( 'id=>parent' === $_fields ) {
    841                         foreach ( $terms as $term ) {
    842                                 $_terms[ $term->term_id ] = $term->parent;
    843                         }
    844                 } elseif ( 'ids' === $_fields ) {
    845                         foreach ( $terms as $term ) {
    846                                 $_terms[] = (int) $term->term_id;
    847                         }
    848                 } elseif ( 'tt_ids' === $_fields ) {
    849                         foreach ( $terms as $term ) {
    850                                 $_terms[] = (int) $term->term_taxonomy_id;
    851                         }
    852                 } elseif ( 'names' === $_fields ) {
    853                         foreach ( $terms as $term ) {
    854                                 $_terms[] = $term->name;
    855                         }
    856                 } elseif ( 'slugs' === $_fields ) {
    857                         foreach ( $terms as $term ) {
    858                                 $_terms[] = $term->slug;
    859                         }
    860                 } elseif ( 'id=>name' === $_fields ) {
    861                         foreach ( $terms as $term ) {
    862                                 $_terms[ $term->term_id ] = $term->name;
    863                         }
    864                 } elseif ( 'id=>slug' === $_fields ) {
    865                         foreach ( $terms as $term ) {
    866                                 $_terms[ $term->term_id ] = $term->slug;
    867                         }
    868                 }
    869 
    870                 if ( ! empty( $_terms ) ) {
    871                         $terms = $_terms;
    872                 }
    873 
    874824                // Hierarchical queries are not limited, so 'offset' and 'number' must be handled now.
    875825                if ( $hierarchical && $number && is_array( $terms ) ) {
    876826                        if ( $offset >= count( $terms ) ) {
    877                                 $terms = array();
     827                                $terms        = array();
     828                                $term_objects = array();
    878829                        } else {
    879                                 $terms = array_slice( $terms, $offset, $number, true );
    880                         }
     830                                $terms        = array_slice( $terms, $offset, $number, true );
     831                                $term_objects = array_slice( $term_objects, $offset, $number, true );
     832                        }
     833                }
     834
     835                // Prime termmeta cache.
     836                if ( $args['update_term_meta_cache'] ) {
     837                        $term_ids = wp_list_pluck( $term_objects, 'term_id' );
     838                        update_termmeta_cache( $term_ids );
    881839                }
    882840
    883841                wp_cache_add( $cache_key, $terms, 'terms' );
    884 
    885                 if ( 'all' === $_fields || 'all_with_object_id' === $_fields ) {
    886                         $terms = $this->populate_terms( $terms );
    887                 }
     842                $terms = $this->format_terms( $term_objects, $_fields );
    888843
    889844                $this->terms = $terms;
    … …  
    951906
    952907        /**
     908         * Format response depending on field requested.
     909         *
     910         * @since 6.0.0
     911         *
     912         * @param WP_Term[] $term_objects Array of term objects.
     913         * @param string $_fields Field to format.
     914         *
     915         * @return WP_Term[]|int[]|string[] Array of terms / strings / ints depending on field requested.
     916         */
     917        protected function format_terms( $term_objects, $_fields ) {
     918                $_terms = array();
     919                if ( 'id=>parent' === $_fields ) {
     920                        foreach ( $term_objects as $term ) {
     921                                $_terms[ $term->term_id ] = $term->parent;
     922                        }
     923                } elseif ( 'ids' === $_fields ) {
     924                        foreach ( $term_objects as $term ) {
     925                                $_terms[] = (int) $term->term_id;
     926                        }
     927                } elseif ( 'tt_ids' === $_fields ) {
     928                        foreach ( $term_objects as $term ) {
     929                                $_terms[] = (int) $term->term_taxonomy_id;
     930                        }
     931                } elseif ( 'names' === $_fields ) {
     932                        foreach ( $term_objects as $term ) {
     933                                $_terms[] = $term->name;
     934                        }
     935                } elseif ( 'slugs' === $_fields ) {
     936                        foreach ( $term_objects as $term ) {
     937                                $_terms[] = $term->slug;
     938                        }
     939                } elseif ( 'id=>name' === $_fields ) {
     940                        foreach ( $term_objects as $term ) {
     941                                $_terms[ $term->term_id ] = $term->name;
     942                        }
     943                } elseif ( 'id=>slug' === $_fields ) {
     944                        foreach ( $term_objects as $term ) {
     945                                $_terms[ $term->term_id ] = $term->slug;
     946                        }
     947                } elseif ( 'all' === $_fields || 'all_with_object_id' === $_fields ) {
     948                        $_terms = $term_objects;
     949                }
     950
     951                return $_terms;
     952        }
     953
     954        /**
    953955         * Generate the ORDER BY clause for an 'orderby' param that is potentially related to a meta query.
    954956         *
    … …  
    10541056         * @since 4.9.8
    10551057         *
    1056          * @param array $term_ids Term IDs.
    1057          * @return array
    1058          */
    1059         protected function populate_terms( $term_ids ) {
    1060                 $terms = array();
    1061 
    1062                 if ( ! is_array( $term_ids ) ) {
    1063                         return $terms;
    1064                 }
    1065 
    1066                 foreach ( $term_ids as $key => $term_id ) {
    1067                         $term = get_term( $term_id );
     1058         * @param Object[]|int[] $terms List of objects or term ids.
     1059         * @return WP_Term[] Array of `WP_Term` objects.
     1060         */
     1061        protected function populate_terms( $terms ) {
     1062                $term_objects = array();
     1063                if ( ! is_array( $terms ) ) {
     1064                        return $term_objects;
     1065                }
     1066
     1067                foreach ( $terms as $key => $term_data ) {
     1068                        if ( is_object( $term_data ) && property_exists( $term_data, 'term_id' ) ) {
     1069                                $term = get_term( $term_data->term_id );
     1070                                if ( property_exists( $term_data, 'object_id' ) ) {
     1071                                        $term->object_id = (int) $term_data->object_id;
     1072                                }
     1073                        } else {
     1074                                $term = get_term( $term_data );
     1075                        }
     1076
    10681077                        if ( $term instanceof WP_Term ) {
    1069                                 $terms[ $key ] = $term;
    1070                         }
    1071                 }
    1072 
    1073                 return $terms;
     1078                                $term_objects[ $key ] = $term;
     1079                        }
     1080                }
     1081
     1082                return $term_objects;
    10741083        }
    10751084}
  • trunk/tests/phpunit/tests/term/cache.php

    r52010 r52836  
    256256                $num_queries = $wpdb->num_queries;
    257257
    258                 $term = get_term_by( 'slug', 'burrito', 'post_tag' );
    259                 $num_queries++;
     258                $term        = get_term_by( 'slug', 'burrito', 'post_tag' );
     259                $num_queries = $num_queries + 2;
    260260                $this->assertSame( 'Taco', $term->name );
    261261                $this->assertSame( $num_queries, $wpdb->num_queries );
    … …  
    287287                $num_queries = $wpdb->num_queries;
    288288
    289                 $term = get_term_by( 'slug', 'burrito', 'post_tag' );
    290                 $num_queries++;
     289                $term        = get_term_by( 'slug', 'burrito', 'post_tag' );
     290                $num_queries = $num_queries + 2;
    291291                $this->assertSame( 'Taco', $term->name );
    292292                $this->assertSame( $num_queries, $wpdb->num_queries );
    … …  
    302302
    303303                // This should not hit cache.
    304                 $term = get_term_by( 'slug', 'burrito', 'post_tag' );
    305                 $num_queries++;
     304                $term        = get_term_by( 'slug', 'burrito', 'post_tag' );
     305                $num_queries = $num_queries + 2;
    306306                $this->assertSame( 'No Taco', $term->name );
    307307                $this->assertSame( $num_queries, $wpdb->num_queries );
    … …  
    326326
    327327                get_term_by( 'name', 'Burrito', 'post_tag' );
    328                 $num_queries++;
     328                $num_queries = $num_queries + 2;
    329329                $this->assertSame( $num_queries, $wpdb->num_queries );
    330330
    … …  
    355355
    356356                get_term_by( 'name', 'Burrito', 'post_tag' );
    357                 $num_queries++;
     357                $num_queries = $num_queries + 2;
    358358                $this->assertSame( $num_queries, $wpdb->num_queries );
    359359
    … …  
    368368                // This should not hit cache.
    369369                get_term_by( 'name', 'burrito', 'post_tag' );
    370                 $num_queries++;
     370                $num_queries = $num_queries + 2;
    371371                $this->assertSame( $num_queries, $wpdb->num_queries );
    372372        }
    … …  
    389389                $last_changed = wp_cache_get( 'last_changed', 'terms' );
    390390
    391                 $term1 = get_term_by( 'name', 'Burrito', 'post_tag' );
    392                 $num_queries++;
     391                $term1       = get_term_by( 'name', 'Burrito', 'post_tag' );
     392                $num_queries = $num_queries + 2;
    393393
    394394                // Verify the term is cached.
    … …  
    432432                $num_queries = $wpdb->num_queries;
    433433
    434                 $term = get_term_by( 'name', 'Burrito', 'post_tag' );
    435                 $num_queries++;
     434                $term        = get_term_by( 'name', 'Burrito', 'post_tag' );
     435                $num_queries = $num_queries + 2;
    436436                $this->assertInstanceOf( 'WP_Term', $term );
    437437                $this->assertSame( $term_id, $term->term_id );
  • trunk/tests/phpunit/tests/term/getTermBy.php

    r52010 r52836  
    55 */
    66class Tests_Term_GetTermBy extends WP_UnitTestCase {
     7
     8        protected $query = '';
    79
    810        public function test_get_term_by_slug() {
    … …  
    124126                $num_queries = $wpdb->num_queries;
    125127                $found       = get_term_by( 'slug', 'foo', 'wptests_tax' );
    126                 $num_queries++;
     128                $num_queries = $num_queries + 2;
    127129
    128130                $this->assertInstanceOf( 'WP_Term', $found );
    … …  
    210212         */
    211213        public function test_query_should_contain_limit_clause() {
    212                 global $wpdb;
    213 
    214214                $term_id = $this->factory->term->create(
    215215                        array(
    … …  
    218218                        )
    219219                );
    220                 $found   = get_term_by( 'name', 'burrito', 'post_tag' );
    221                 $this->assertSame( $term_id, $found->term_id );
    222                 $this->assertStringContainsString( 'LIMIT 1', $wpdb->last_query );
     220                add_filter( 'terms_pre_query', array( $this, 'get_query_from_filter' ), 10, 2 );
     221                $found = get_term_by( 'name', 'burrito', 'post_tag' );
     222                $this->assertSame( $term_id, $found->term_id );
     223                $this->assertStringContainsString( 'LIMIT 1', $this->query );
    223224        }
    224225
    … …  
    283284                $this->assertFalse( $found_by_name );
    284285        }
     286
     287        public function get_query_from_filter( $terms, $wp_term_query ) {
     288                $this->query = $wp_term_query->request;
     289
     290                return $terms;
     291        }
    285292}
  • trunk/tests/phpunit/tests/term/getTerms.php

    r52010 r52836  
    119119                $time1 = wp_cache_get( 'last_changed', 'terms' );
    120120                $this->assertNotEmpty( $time1 );
    121                 $this->assertSame( $num_queries + 1, $wpdb->num_queries );
     121                $this->assertSame( $num_queries + 2, $wpdb->num_queries );
    122122
    123123                $num_queries = $wpdb->num_queries;
  • trunk/tests/phpunit/tests/term/isObjectInTerm.php

    r46586 r52836  
    146146                $num_queries = $wpdb->num_queries;
    147147                $this->assertTrue( is_object_in_term( $o, 'wptests_tax', $terms[0] ) );
    148                 $num_queries++;
     148                $num_queries = $num_queries + 2;
    149149                $this->assertSame( $num_queries, $wpdb->num_queries );
    150150
    … …  
    167167                $num_queries = $wpdb->num_queries;
    168168                $this->assertTrue( is_object_in_term( $o, 'wptests_tax', $terms[0] ) );
    169                 $num_queries++;
     169                $num_queries = $num_queries + 2;
    170170                $this->assertSame( $num_queries, $wpdb->num_queries );
    171171
    … …  
    174174                $num_queries = $wpdb->num_queries;
    175175                $this->assertTrue( is_object_in_term( $o, 'wptests_tax', $terms[1] ) );
    176                 $num_queries++;
     176                $num_queries = $num_queries + 2;
    177177                $this->assertSame( $num_queries, $wpdb->num_queries );
    178178        }
Note: See TracChangeset for help on using the changeset viewer.