Make WordPress Core

Changeset 34529


Ignore:
Timestamp:
09/25/2015 03:58:59 AM (11 years ago)
Author:
boonebgorges
Message:

Introduce metadata for taxonomy terms.

Adds a new table to the database schema (wp_termmeta), and a set of
*_term_meta() API functions. get_terms() and wp_get_object_terms()
now also support 'meta_query' parameters, with syntax identical to other
uses of WP_Meta_Query.

When fetching terms via get_terms() or wp_get_object_terms(), metadata for
matched terms is preloaded into the cache by default. Disable this behavior
by setting the new $update_term_meta_cache paramater to false.

To maximize performance, within WP_Query loops, the termmeta cache is *not*
primed by default. Instead, we use a lazy-loading technique: metadata for all
terms belonging to posts in the loop is loaded into the cache the first time
that get_term_meta() is called within the loop.

Props boonebgorges, sirzooro.
See #10142.

Location:
trunk
Files:
1 added
8 edited

Legend:

Unmodified
Added
Removed
  • trunk/src/wp-admin/includes/schema.php

    r34298 r34529  
    5858
    5959        // Blog specific tables.
    60         $blog_tables = "CREATE TABLE $wpdb->terms (
     60        $blog_tables = "CREATE TABLE $wpdb->termmeta (
     61  meta_id bigint(20) unsigned NOT NULL auto_increment,
     62  term_id bigint(20) unsigned NOT NULL default '0',
     63  meta_key varchar(255) default NULL,
     64  meta_value longtext,
     65  PRIMARY KEY (meta_id),
     66  KEY term_id (term_id),
     67  KEY meta_key (meta_key($max_index_length))
     68) $charset_collate;
     69CREATE TABLE $wpdb->terms (
    6170 term_id bigint(20) unsigned NOT NULL auto_increment,
    6271 name varchar(200) NOT NULL default '',
  • trunk/src/wp-admin/includes/upgrade.php

    r34030 r34529  
    26292629                $wpdb->query( "ALTER TABLE $wpdb->posts DROP INDEX post_name, ADD INDEX post_name(post_name(191))" );
    26302630        }
     2631
     2632        // Upgrade versions prior to 4.4.
     2633        if ( $wp_current_db_version < 34370 ) {
     2634                // If compatible termmeta table is found, use it, but enforce a proper index.
     2635                if ( $wpdb->get_var( "SHOW TABLES LIKE '{$wpdb->termmeta}'" ) && $wpdb->get_results( "SHOW INDEX FROM {$wpdb->termmeta} WHERE Column_name = 'meta_key'" ) ) {
     2636                        $wpdb->query( "ALTER TABLE $wpdb->termmeta DROP INDEX meta_key, ADD INDEX meta_key(meta_key(191))" );
     2637                }
     2638        }
    26312639}
    26322640
  • trunk/src/wp-includes/default-filters.php

    r34270 r34529  
    202202add_filter( 'title_save_pre',           'trim'                                );
    203203add_filter( 'get_comment_metadata',     'wp_lazyload_comment_meta',     10, 2 );
     204add_filter( 'get_term_metadata',        'wp_lazyload_term_meta',        10, 2 );
    204205
    205206add_filter( 'http_request_host_is_external', 'allowed_http_request_hosts', 10, 2 );
  • trunk/src/wp-includes/taxonomy-functions.php

    r34516 r34529  
    958958 * @since 4.2.0 Introduced 'name' and 'childless' parameters.
    959959 * @since 4.4.0 Introduced the ability to pass 'term_id' as an alias of 'id' for the `orderby` parameter.
     960 *              Introduced the 'meta_query' and 'update_term_meta_cache' parameters.
    960961 *
    961962 * @global wpdb  $wpdb WordPress database abstraction object.
     
    10141015 *     @type string       $cache_domain      Unique cache key to be produced when this query is stored in an
    10151016 *                                           object cache. Default is 'core'.
     1017 *     @type bool         $update_term_meta_cache Whether to prime meta caches for matched terms. Default true.
     1018 *     @type array        $meta_query             Meta query clauses to limit retrieved terms by.
     1019 *                                                See `WP_Meta_Query`. Default empty.
    10161020 * }
    10171021 * @return array|int|WP_Error List of Term Objects and their children. Will return WP_Error, if any of $taxonomies
     
    10371041                'number' => '', 'fields' => 'all', 'name' => '', 'slug' => '', 'parent' => '', 'childless' => false,
    10381042                'hierarchical' => true, 'child_of' => 0, 'get' => '', 'name__like' => '', 'description__like' => '',
    1039                 'pad_counts' => false, 'offset' => '', 'search' => '', 'cache_domain' => 'core' );
     1043                'pad_counts' => false, 'offset' => '', 'search' => '', 'cache_domain' => 'core',
     1044                'update_term_meta_cache' => true, 'meta_query' => '' );
    10401045        $args = wp_parse_args( $args, $defaults );
    10411046        $args['number'] = absint( $args['number'] );
     
    12951300                $like = '%' . $wpdb->esc_like( $args['search'] ) . '%';
    12961301                $where .= $wpdb->prepare( ' AND ((t.name LIKE %s) OR (t.slug LIKE %s))', $like, $like );
     1302        }
     1303
     1304        // Meta query support.
     1305        $join = '';
     1306        if ( ! empty( $args['meta_query'] ) ) {
     1307                $mquery = new WP_Meta_Query( $args['meta_query'] );
     1308                $mq_sql = $mquery->get_sql( 'term', 't', 'term_id' );
     1309
     1310                $join  .= $mq_sql['join'];
     1311                $where .= $mq_sql['where'];
    12971312        }
    12981313
     
    13421357        $fields = implode( ', ', apply_filters( 'get_terms_fields', $selects, $args, $taxonomies ) );
    13431358
    1344         $join = "INNER JOIN $wpdb->term_taxonomy AS tt ON t.term_id = tt.term_id";
     1359        $join .= " INNER JOIN $wpdb->term_taxonomy AS tt ON t.term_id = tt.term_id";
    13451360
    13461361        $pieces = array( 'fields', 'join', 'where', 'orderby', 'order', 'limits' );
     
    13731388        if ( 'all' == $_fields ) {
    13741389                update_term_cache( $terms );
     1390        }
     1391
     1392        // Prime termmeta cache.
     1393        if ( $args['update_term_meta_cache'] ) {
     1394                $term_ids = wp_list_pluck( $terms, 'term_id' );
     1395                update_termmeta_cache( $term_ids );
    13751396        }
    13761397
     
    14561477
    14571478/**
     1479 * Adds metadata to a term.
     1480 *
     1481 * @since 4.4.0
     1482 *
     1483 * @param int    $term_id    Term ID.
     1484 * @param string $meta_key   Metadata name.
     1485 * @param mixed  $meta_value Metadata value.
     1486 * @param bool   $unique     Optional. Whether to bail if an entry with the same key is found for the term.
     1487 *                           Default false.
     1488 * @return int|bool Meta ID on success, false on failure.
     1489 */
     1490function add_term_meta( $term_id, $meta_key, $meta_value, $unique = false ) {
     1491        return add_metadata( 'term', $term_id, $meta_key, $meta_value, $unique );
     1492}
     1493
     1494/**
     1495 * Removes metadata matching criteria from a term.
     1496 *
     1497 * @since 4.4.0
     1498 *
     1499 * @param int    $term_id    Term ID.
     1500 * @param string $meta_key   Metadata name.
     1501 * @param mixed  $meta_value Optional. Metadata value. If provided, rows will only be removed that match the value.
     1502 * @return bool True on success, false on failure.
     1503 */
     1504function delete_term_meta( $term_id, $meta_key, $meta_value = '' ) {
     1505        return delete_metadata( 'term', $term_id, $meta_key, $meta_value );
     1506}
     1507
     1508/**
     1509 * Retrieves metadata for a term.
     1510 *
     1511 * @since 4.4.0
     1512 *
     1513 * @param int    $term_id Term ID.
     1514 * @param string $key     Optional. The meta key to retrieve. If no key is provided, fetches all metadata for the term.
     1515 * @param bool   $single  Whether to return a single value. If false, an array of all values matching the
     1516 *                        `$term_id`/`$key` pair will be returned. Default: false.
     1517 * @return mixed If `$single` is false, an array of metadata values. If `$single` is true, a single metadata value.
     1518 */
     1519function get_term_meta( $term_id, $key = '', $single = false ) {
     1520        return get_metadata( 'term', $term_id, $key, $single );
     1521}
     1522
     1523/**
     1524 * Updates term metadata.
     1525 *
     1526 * Use the `$prev_value` parameter to differentiate between meta fields with the same key and term ID.
     1527 *
     1528 * If the meta field for the term does not exist, it will be added.
     1529 *
     1530 * @since 4.4.0
     1531 *
     1532 * @param int    $term_id    Term ID.
     1533 * @param string $meta_key   Metadata key.
     1534 * @param mixed  $meta_value Metadata value.
     1535 * @param mixed  $prev_value Optional. Previous value to check before removing.
     1536 * @return int|bool Meta ID if the key didn't previously exist. True on successful update. False on failure.
     1537 */
     1538function update_term_meta( $term_id, $meta_key, $meta_value, $prev_value = '' ) {
     1539        return update_metadata( 'term', $term_id, $meta_key, $meta_value, $prev_value );
     1540}
     1541
     1542/**
     1543 * Updates metadata cache for list of term IDs.
     1544 *
     1545 * Performs SQL query to retrieve all metadata for the terms matching `$term_ids` and stores them in the cache.
     1546 * Subsequent calls to `get_term_meta()` will not need to query the database.
     1547 *
     1548 * @since 4.4.0
     1549 *
     1550 * @param array $term_ids List of term IDs.
     1551 * @return array|false Returns false if there is nothing to update. Returns an array of metadata on success.
     1552 */
     1553function update_termmeta_cache( $term_ids ) {
     1554        return update_meta_cache( 'term', $term_ids );
     1555}
     1556
     1557/**
     1558 * Lazy-loads termmeta when inside of a `WP_Query` loop.
     1559 *
     1560 * As a rule, term queries (`get_terms()` and `wp_get_object_terms()`) prime the metadata cache for matched terms by
     1561 * default. However, this can cause a slight performance penalty, especially when that metadata is not actually used.
     1562 * In the context of a `WP_Query` loop, we're able to avoid this potential penalty. `update_object_term_cache()`,
     1563 * called from `update_post_caches()`, does not 'update_term_meta_cache'. Instead, the first time `get_term_meta()` is
     1564 * called from within a `WP_Query` loop, the current function detects the fact, and then primes the metadata cache for
     1565 * all terms attached to all posts in the loop, with a single database query.
     1566 *
     1567 * @since 4.4.0
     1568 *
     1569 * @param null $check   The `$check` param passed from the 'pre_term_metadata' hook.
     1570 * @param int  $term_id ID of the term whose metadata is being cached.
     1571 * @return null In order not to short-circuit `get_metadata()`.
     1572 */
     1573function wp_lazyload_term_meta( $check, $term_id ) {
     1574        global $wp_query;
     1575
     1576        if ( $wp_query instanceof WP_Query && ! empty( $wp_query->posts ) && $wp_query->get( 'update_post_term_cache' ) ) {
     1577                // We can only lazyload if the entire post object is present.
     1578                $posts = array();
     1579                foreach ( $wp_query->posts as $post ) {
     1580                        if ( $post instanceof WP_Post ) {
     1581                                $posts[] = $post;
     1582                        }
     1583                }
     1584
     1585                if ( empty( $posts ) ) {
     1586                        return;
     1587                }
     1588
     1589                // Fetch cached term_ids for each post. Keyed by term_id for faster lookup.
     1590                $term_ids = array();
     1591                foreach ( $posts as $post ) {
     1592                        $taxonomies = get_object_taxonomies( $post->post_type );
     1593                        foreach ( $taxonomies as $taxonomy ) {
     1594                                // No extra queries. Term cache should already be primed by 'update_post_term_cache'.
     1595                                $terms = get_object_term_cache( $post->ID, $taxonomy );
     1596                                if ( false !== $terms ) {
     1597                                        foreach ( $terms as $term ) {
     1598                                                if ( ! isset( $term_ids[ $term->term_id ] ) ) {
     1599                                                        $term_ids[ $term->term_id ] = 1;
     1600                                                }
     1601                                        }
     1602                                }
     1603                        }
     1604                }
     1605
     1606                if ( $term_ids ) {
     1607                        update_termmeta_cache( array_keys( $term_ids ) );
     1608                }
     1609        }
     1610
     1611        return $check;
     1612}
     1613
     1614/**
    14581615 * Check if Term exists.
    14591616 *
     
    20122169 * @since 4.2.0 Added support for 'taxonomy', 'parent', and 'term_taxonomy_id' values of `$orderby`.
    20132170 *              Introduced `$parent` argument.
     2171 * @since 4.4.0 Introduced `$meta_query` and `$update_term_meta_cache` arguments.
    20142172 *
    20152173 * @global wpdb $wpdb WordPress database abstraction object.
     
    20272185 *                           of strings.
    20282186 *     @type int    $parent  Optional. Limit results to the direct children of a given term ID.
     2187 *     @type bool   $update_term_meta_cache Whether to prime termmeta cache for matched terms. Only applies when
     2188 *                                          `$fields` is 'all', 'all_with_object_id', or 'term_id'. Default true.
     2189 *     @type array  $meta_query             Meta query clauses to limit retrieved terms by. See `WP_Meta_Query`.
     2190 *                                          Default empty.
    20292191 * }
    20302192 * @return array|WP_Error The requested term data or empty array if no terms found.
     
    20542216                'fields'  => 'all',
    20552217                'parent'  => '',
     2218                'update_term_meta_cache' => true,
     2219                'meta_query' => '',
    20562220        );
    20572221        $args = wp_parse_args( $args, $defaults );
     
    21272291        }
    21282292
     2293        // Meta query support.
     2294        $meta_query_join = '';
     2295        if ( ! empty( $args['meta_query'] ) ) {
     2296                $mquery = new WP_Meta_Query( $args['meta_query'] );
     2297                $mq_sql = $mquery->get_sql( 'term', 't', 'term_id' );
     2298
     2299                $meta_query_join .= $mq_sql['join'];
     2300
     2301                // Strip leading AND.
     2302                $where[] = preg_replace( '/^\s*AND/', '', $mq_sql['where'] );
     2303        }
     2304
    21292305        $where = implode( ' AND ', $where );
    21302306
    2131         $query = "SELECT $select_this FROM $wpdb->terms AS t INNER JOIN $wpdb->term_taxonomy AS tt ON tt.term_id = t.term_id INNER JOIN $wpdb->term_relationships AS tr ON tr.term_taxonomy_id = tt.term_taxonomy_id WHERE $where $orderby $order";
     2307        $query = "SELECT $select_this FROM $wpdb->terms AS t INNER JOIN $wpdb->term_taxonomy AS tt ON tt.term_id = t.term_id INNER JOIN $wpdb->term_relationships AS tr ON tr.term_taxonomy_id = tt.term_taxonomy_id $meta_query_join WHERE $where $orderby $order";
    21322308
    21332309        $objects = false;
     
    21522328                        $terms[$key] = sanitize_term_field( 'term_taxonomy_id', $tt_id, 0, $taxonomy, 'raw' ); // 0 should be the term id, however is not needed when using raw context.
    21532329                }
     2330        }
     2331
     2332        // Update termmeta cache, if necessary.
     2333        if ( $args['update_term_meta_cache'] && ( 'all' === $fields || 'all_with_object_ids' === $fields || 'term_id' === $fields ) ) {
     2334                if ( 'term_id' === $fields ) {
     2335                        $term_ids = $fields;
     2336                } else {
     2337                        $term_ids = wp_list_pluck( $terms, 'term_id' );
     2338                }
     2339
     2340                update_termmeta_cache( $term_ids );
    21542341        }
    21552342
     
    32893476                'fields' => 'all_with_object_id',
    32903477                'orderby' => 'none',
     3478                'update_term_meta_cache' => false,
    32913479        ) );
    32923480
  • trunk/src/wp-includes/version.php

    r34030 r34529  
    1212 * @global int $wp_db_version
    1313 */
    14 $wp_db_version = 34030;
     14$wp_db_version = 34528;
    1515
    1616/**
  • trunk/src/wp-includes/wp-db.php

    r34478 r34529  
    267267         */
    268268        var $tables = array( 'posts', 'comments', 'links', 'options', 'postmeta',
    269                 'terms', 'term_taxonomy', 'term_relationships', 'commentmeta' );
     269                'terms', 'term_taxonomy', 'term_relationships', 'termmeta', 'commentmeta' );
    270270
    271271        /**
     
    382382         */
    383383        public $term_taxonomy;
     384
     385        /**
     386         * WordPress Term Meta table.
     387         *
     388         * @since 4.4.0
     389         * @access public
     390         * @var string
     391         */
     392        public $termmeta;
    384393
    385394        /*
  • trunk/tests/phpunit/tests/term/getTerms.php

    r33903 r34529  
    2323
    2424                // last_changed and num_queries should bump
    25                 $terms = get_terms( 'post_tag' );
     25                $terms = get_terms( 'post_tag', array( 'update_term_meta_cache' => false ) );
    2626                $this->assertEquals( 3, count( $terms ) );
    2727                $time1 = wp_cache_get( 'last_changed', 'terms' );
     
    3232
    3333                // Again. last_changed and num_queries should remain the same.
    34                 $terms = get_terms( 'post_tag' );
     34                $terms = get_terms( 'post_tag', array( 'update_term_meta_cache' => false ) );
    3535                $this->assertEquals( 3, count( $terms ) );
    3636                $this->assertEquals( $time1, wp_cache_get( 'last_changed', 'terms' ) );
     
    15031503        }
    15041504
     1505        /**
     1506         * @ticket 10142
     1507         */
     1508        public function test_termmeta_cache_should_be_primed_by_default() {
     1509                global $wpdb;
     1510
     1511                register_taxonomy( 'wptests_tax', 'post' );
     1512                $terms = $this->factory->term->create_many( 3, array( 'taxonomy' => 'wptests_tax' ) );
     1513                add_term_meta( $terms[0], 'foo', 'bar' );
     1514                add_term_meta( $terms[1], 'foo', 'bar' );
     1515                add_term_meta( $terms[2], 'foo', 'bar' );
     1516
     1517                $found = get_terms( 'wptests_tax', array(
     1518                        'hide_empty' => false,
     1519                        'include' => $terms,
     1520                ) );
     1521
     1522                $num_queries = $wpdb->num_queries;
     1523
     1524                foreach ( $terms as $t ) {
     1525                        $this->assertSame( 'bar', get_term_meta( $t, 'foo', true ) );
     1526                }
     1527
     1528                $this->assertSame( $num_queries, $wpdb->num_queries );
     1529        }
     1530
     1531        /**
     1532         * @ticket 10142
     1533         */
     1534        public function test_termmeta_cache_should_not_be_primed_when_update_term_meta_cache_is_false() {
     1535                global $wpdb;
     1536
     1537                register_taxonomy( 'wptests_tax', 'post' );
     1538                $terms = $this->factory->term->create_many( 3, array( 'taxonomy' => 'wptests_tax' ) );
     1539                add_term_meta( $terms[0], 'foo', 'bar' );
     1540                add_term_meta( $terms[1], 'foo', 'bar' );
     1541                add_term_meta( $terms[2], 'foo', 'bar' );
     1542
     1543                $found = get_terms( 'wptests_tax', array(
     1544                        'hide_empty' => false,
     1545                        'include' => $terms,
     1546                        'update_term_meta_cache' => false,
     1547                ) );
     1548
     1549                $num_queries = $wpdb->num_queries;
     1550
     1551                foreach ( $terms as $t ) {
     1552                        $this->assertSame( 'bar', get_term_meta( $t, 'foo', true ) );
     1553                }
     1554
     1555                $this->assertSame( $num_queries + 3, $wpdb->num_queries );
     1556        }
     1557
     1558        /**
     1559         * @ticket 10142
     1560         */
     1561        public function test_meta_query() {
     1562                register_taxonomy( 'wptests_tax', 'post' );
     1563                $terms = $this->factory->term->create_many( 5, array( 'taxonomy' => 'wptests_tax' ) );
     1564                add_term_meta( $terms[0], 'foo', 'bar' );
     1565                add_term_meta( $terms[1], 'foo', 'bar' );
     1566                add_term_meta( $terms[2], 'foo', 'baz' );
     1567                add_term_meta( $terms[3], 'foob', 'ar' );
     1568
     1569                $found = get_terms( 'wptests_tax', array(
     1570                        'hide_empty' => false,
     1571                        'meta_query' => array(
     1572                                array(
     1573                                        'key' => 'foo',
     1574                                        'value' => 'bar',
     1575                                ),
     1576                        ),
     1577                        'fields' => 'ids',
     1578                ) );
     1579
     1580                $this->assertEqualSets( array( $terms[0], $terms[1] ), $found );
     1581        }
     1582
    15051583        protected function create_hierarchical_terms_and_posts() {
    15061584                $terms = array();
  • trunk/tests/phpunit/tests/term/wpGetObjectTerms.php

    r31270 r34529  
    419419        }
    420420
     421        /**
     422         * @ticket 10142
     423         */
     424        public function test_termmeta_cache_should_be_primed_by_default() {
     425                global $wpdb;
     426
     427                register_taxonomy( 'wptests_tax', 'post' );
     428                $terms = $this->factory->term->create_many( 3, array( 'taxonomy' => 'wptests_tax' ) );
     429                add_term_meta( $terms[0], 'foo', 'bar' );
     430                add_term_meta( $terms[1], 'foo', 'bar' );
     431                add_term_meta( $terms[2], 'foo', 'bar' );
     432
     433                $p = $this->factory->post->create();
     434                wp_set_object_terms( $p, $terms, 'wptests_tax' );
     435
     436                $found = wp_get_object_terms( $p, 'wptests_tax' );
     437
     438                $num_queries = $wpdb->num_queries;
     439
     440                foreach ( $terms as $t ) {
     441                        $this->assertSame( 'bar', get_term_meta( $t, 'foo', true ) );
     442                }
     443
     444                $this->assertSame( $num_queries, $wpdb->num_queries );
     445        }
     446
     447        /**
     448         * @ticket 10142
     449         */
     450        public function test_termmeta_cache_should_not_be_primed_when_update_term_meta_cache_is_false() {
     451                global $wpdb;
     452
     453                register_taxonomy( 'wptests_tax', 'post' );
     454                $terms = $this->factory->term->create_many( 3, array( 'taxonomy' => 'wptests_tax' ) );
     455                add_term_meta( $terms[0], 'foo', 'bar' );
     456                add_term_meta( $terms[1], 'foo', 'bar' );
     457                add_term_meta( $terms[2], 'foo', 'bar' );
     458
     459                $p = $this->factory->post->create();
     460                wp_set_object_terms( $p, $terms, 'wptests_tax' );
     461
     462                $found = wp_get_object_terms( $p, 'wptests_tax', array(
     463                        'update_term_meta_cache' => false,
     464                ) );
     465
     466                $num_queries = $wpdb->num_queries;
     467
     468                foreach ( $terms as $t ) {
     469                        $this->assertSame( 'bar', get_term_meta( $t, 'foo', true ) );
     470                }
     471
     472                $this->assertSame( $num_queries + 3, $wpdb->num_queries );
     473        }
     474
     475        /**
     476         * @ticket 10142
     477         */
     478        public function test_meta_query() {
     479                register_taxonomy( 'wptests_tax', 'post' );
     480                $terms = $this->factory->term->create_many( 5, array( 'taxonomy' => 'wptests_tax' ) );
     481                add_term_meta( $terms[0], 'foo', 'bar' );
     482                add_term_meta( $terms[1], 'foo', 'bar' );
     483                add_term_meta( $terms[2], 'foo', 'baz' );
     484                add_term_meta( $terms[3], 'foob', 'ar' );
     485
     486                $p = $this->factory->post->create();
     487                wp_set_object_terms( $p, $terms, 'wptests_tax' );
     488
     489                $found = wp_get_object_terms( $p, 'wptests_tax', array(
     490                        'meta_query' => array(
     491                                array(
     492                                        'key' => 'foo',
     493                                        'value' => 'bar',
     494                                ),
     495                        ),
     496                ) );
     497
     498                $this->assertEqualSets( array( $terms[0], $terms[1] ), wp_list_pluck( $found, 'term_id' ) );
     499        }
     500
    421501        public function filter_get_object_terms( $terms ) {
    422502                $term_ids = wp_list_pluck( $terms, 'term_id' );
Note: See TracChangeset for help on using the changeset viewer.