Make WordPress Core

Changeset 52921


Ignore:
Timestamp:
03/11/2022 11:05:02 AM (2 years ago)
Author:
spacedmonkey
Message:

Taxonomy: Use get_terms instead of a database lookup in term_exists().

Replace raw SQL queries to the terms table, with a call to the get_terms function. Using get_terms means that term_exists is now cached. For developers using term_exists where cache invalidation is disabled, such as importing, a workaround was added to ensure that queries are uncached.

Props Spacedmonkey, boonebgorges, flixos90, peterwilsoncc.
Fixes #36949.

Location:
trunk
Files:
5 edited

Legend:

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

    r52830 r52921  
    15201520 *
    15211521 * @since 3.0.0
    1522  *
    1523  * @global wpdb $wpdb WordPress database abstraction object.
     1522 * @since 6.0.0 Converted to use `get_terms()`.
     1523 *
     1524 * @global bool $_wp_suspend_cache_invalidation
    15241525 *
    15251526 * @param int|string $term     The term to check. Accepts term ID, slug, or name.
     
    15321533 */
    15331534function term_exists( $term, $taxonomy = '', $parent = null ) {
    1534     global $wpdb;
     1535    global $_wp_suspend_cache_invalidation;
    15351536
    15361537    if ( null === $term ) {
     
    15381539    }
    15391540
    1540     $select     = "SELECT term_id FROM $wpdb->terms as t WHERE ";
    1541     $tax_select = "SELECT tt.term_id, tt.term_taxonomy_id FROM $wpdb->terms AS t INNER JOIN $wpdb->term_taxonomy as tt ON tt.term_id = t.term_id WHERE ";
     1541    $defaults = array(
     1542        'get'                    => 'all',
     1543        'fields'                 => 'ids',
     1544        'number'                 => 1,
     1545        'update_term_meta_cache' => false,
     1546        'order'                  => 'ASC',
     1547        'orderby'                => 'term_id',
     1548        'suppress_filter'        => true,
     1549    );
     1550
     1551    // Ensure that while importing, queries are not cached.
     1552    if ( ! empty( $_wp_suspend_cache_invalidation ) ) {
     1553        // @todo Disable caching once #52710 is merged.
     1554        $defaults['cache_domain'] = microtime();
     1555    }
     1556
     1557    if ( ! empty( $taxonomy ) ) {
     1558        $defaults['taxonomy'] = $taxonomy;
     1559        $defaults['fields']   = 'all';
     1560    }
    15421561
    15431562    if ( is_int( $term ) ) {
     
    15451564            return 0;
    15461565        }
    1547         $where = 't.term_id = %d';
    1548         if ( ! empty( $taxonomy ) ) {
    1549             // phpcs:ignore WordPress.DB.PreparedSQLPlaceholders.ReplacementsWrongNumber
    1550             return $wpdb->get_row( $wpdb->prepare( $tax_select . $where . ' AND tt.taxonomy = %s', $term, $taxonomy ), ARRAY_A );
    1551         } else {
    1552             return $wpdb->get_var( $wpdb->prepare( $select . $where, $term ) );
    1553         }
    1554     }
    1555 
    1556     $term = trim( wp_unslash( $term ) );
    1557     $slug = sanitize_title( $term );
    1558 
    1559     $where             = 't.slug = %s';
    1560     $else_where        = 't.name = %s';
    1561     $where_fields      = array( $slug );
    1562     $else_where_fields = array( $term );
    1563     $orderby           = 'ORDER BY t.term_id ASC';
    1564     $limit             = 'LIMIT 1';
     1566        $args  = wp_parse_args( array( 'include' => array( $term ) ), $defaults );
     1567        $terms = get_terms( $args );
     1568    } else {
     1569        $term = trim( wp_unslash( $term ) );
     1570        if ( '' === $term ) {
     1571            return null;
     1572        }
     1573
     1574        if ( ! empty( $taxonomy ) && is_numeric( $parent ) ) {
     1575            $defaults['parent'] = (int) $parent;
     1576        }
     1577
     1578        $args  = wp_parse_args( array( 'slug' => sanitize_title( $term ) ), $defaults );
     1579        $terms = get_terms( $args );
     1580        if ( empty( $terms ) || is_wp_error( $terms ) ) {
     1581            $args  = wp_parse_args( array( 'name' => $term ), $defaults );
     1582            $terms = get_terms( $args );
     1583        }
     1584    }
     1585
     1586    if ( empty( $terms ) || is_wp_error( $terms ) ) {
     1587        return null;
     1588    }
     1589
     1590    $_term = array_shift( $terms );
     1591
    15651592    if ( ! empty( $taxonomy ) ) {
    1566         if ( is_numeric( $parent ) ) {
    1567             $parent              = (int) $parent;
    1568             $where_fields[]      = $parent;
    1569             $else_where_fields[] = $parent;
    1570             $where              .= ' AND tt.parent = %d';
    1571             $else_where         .= ' AND tt.parent = %d';
    1572         }
    1573 
    1574         $where_fields[]      = $taxonomy;
    1575         $else_where_fields[] = $taxonomy;
    1576 
    1577         $result = $wpdb->get_row( $wpdb->prepare( "SELECT tt.term_id, tt.term_taxonomy_id FROM $wpdb->terms AS t INNER JOIN $wpdb->term_taxonomy as tt ON tt.term_id = t.term_id WHERE $where AND tt.taxonomy = %s $orderby $limit", $where_fields ), ARRAY_A );
    1578         if ( $result ) {
    1579             return $result;
    1580         }
    1581 
    1582         return $wpdb->get_row( $wpdb->prepare( "SELECT tt.term_id, tt.term_taxonomy_id FROM $wpdb->terms AS t INNER JOIN $wpdb->term_taxonomy as tt ON tt.term_id = t.term_id WHERE $else_where AND tt.taxonomy = %s $orderby $limit", $else_where_fields ), ARRAY_A );
    1583     }
    1584 
    1585     // phpcs:ignore WordPress.DB.PreparedSQLPlaceholders.UnfinishedPrepare
    1586     $result = $wpdb->get_var( $wpdb->prepare( "SELECT term_id FROM $wpdb->terms as t WHERE $where $orderby $limit", $where_fields ) );
    1587     if ( $result ) {
    1588         return $result;
    1589     }
    1590 
    1591     // phpcs:ignore WordPress.DB.PreparedSQLPlaceholders.UnfinishedPrepare
    1592     return $wpdb->get_var( $wpdb->prepare( "SELECT term_id FROM $wpdb->terms as t WHERE $else_where $orderby $limit", $else_where_fields ) );
     1593        return array(
     1594            'term_id'          => (string) $_term->term_id,
     1595            'term_taxonomy_id' => (string) $_term->term_taxonomy_id,
     1596        );
     1597    }
     1598
     1599    return (string) $_term;
    15931600}
    15941601
  • trunk/tests/phpunit/tests/term/getTerm.php

    r51568 r52921  
    3131            array( '%d' )
    3232        );
     33
     34        clean_term_cache( $t1['term_id'] );
    3335
    3436        return array(
  • trunk/tests/phpunit/tests/term/splitSharedTerm.php

    r52389 r52921  
    5555            array( '%d' )
    5656        );
     57        clean_term_cache( $t1['term_id'], 'category' );
    5758
    5859        $t2_child = wp_insert_term(
     
    152153            array( '%d' )
    153154        );
     155        clean_term_cache( $t1['term_id'], 'category' );
    154156        $th = _get_term_hierarchy( 'wptests_tax_4' );
    155157
     
    180182            array( '%d' )
    181183        );
     184        clean_term_cache( $t1['term_id'], 'category' );
    182185
    183186        $this->assertSame( $t1['term_id'], get_option( 'default_category', -1 ) );
     
    208211            array( '%d' )
    209212        );
     213        clean_term_cache( $t1['term_id'], 'category' );
    210214
    211215        $menu_id       = wp_create_nav_menu( 'Nav Menu Bar' );
     
    246250            array( 'term_taxonomy_id' => $nav_term->term_taxonomy_id )
    247251        );
     252        clean_term_cache( $shared_term_id, 'category' );
    248253
    249254        set_theme_mod( 'nav_menu_locations', array( 'foo' => $shared_term_id ) );
     
    275280            array( 'term_taxonomy_id' => $nav_term->term_taxonomy_id )
    276281        );
     282        clean_term_cache( $shared_term_id, 'category' );
    277283
    278284        $t1            = wp_insert_term( 'Random term', 'category' );
  • trunk/tests/phpunit/tests/term/termExists.php

    r52389 r52921  
    272272    }
    273273
     274    /**
     275     * @ticket 36949
     276     * @covers ::term_exists()
     277     */
     278    public function test_term_lookup_by_id_and_update() {
     279        register_taxonomy( 'wptests_tax', 'post' );
     280
     281        $slug = __FUNCTION__;
     282        $t    = self::factory()->term->create(
     283            array(
     284                'slug'     => $slug,
     285                'taxonomy' => 'wptests_tax',
     286            )
     287        );
     288        $this->assertEquals( $t, term_exists( $t ) );
     289        $this->assertTrue( wp_delete_term( $t, 'wptests_tax' ) );
     290        $this->assertNull( term_exists( $t ) );
     291
     292        // Clean up.
     293        _unregister_taxonomy( 'wptests_tax' );
     294    }
     295
     296    /**
     297     * @ticket 36949
     298     * @covers ::term_exists()
     299     */
     300    public function test_term_lookup_by_slug_and_update() {
     301        register_taxonomy( 'wptests_tax', 'post' );
     302
     303        $slug = __FUNCTION__;
     304        $t    = self::factory()->term->create(
     305            array(
     306                'slug'     => $slug,
     307                'taxonomy' => 'wptests_tax',
     308            )
     309        );
     310        $this->assertEquals( $t, term_exists( $slug ) );
     311        $this->assertTrue( wp_delete_term( $t, 'wptests_tax' ) );
     312        $this->assertNull( term_exists( $slug ) );
     313
     314        // Clean up.
     315        _unregister_taxonomy( 'wptests_tax' );
     316    }
     317
     318    /**
     319     * @ticket 36949
     320     * @covers ::term_exists()
     321     */
     322    public function test_term_exists_caching() {
     323        global $wpdb;
     324        register_taxonomy( 'wptests_tax', 'post' );
     325
     326        $slug = __FUNCTION__;
     327        $t    = self::factory()->term->create(
     328            array(
     329                'slug'     => $slug,
     330                'taxonomy' => 'wptests_tax',
     331            )
     332        );
     333        $this->assertEquals( $t, term_exists( $slug ) );
     334        $num_queries = $wpdb->num_queries;
     335        $this->assertEquals( $t, term_exists( $slug ) );
     336        $this->assertSame( $num_queries, $wpdb->num_queries );
     337
     338        $this->assertTrue( wp_delete_term( $t, 'wptests_tax' ) );
     339        $num_queries = $wpdb->num_queries;
     340        $this->assertNull( term_exists( $slug ) );
     341        $this->assertSame( $num_queries + 2, $wpdb->num_queries );
     342
     343        // Clean up.
     344        _unregister_taxonomy( 'wptests_tax' );
     345    }
     346
     347    /**
     348     * @ticket 36949
     349     * @covers ::term_exists()
     350     */
     351    public function test_term_exists_caching_suspend_cache_invalidation() {
     352        global $wpdb;
     353        register_taxonomy( 'wptests_tax', 'post' );
     354
     355        wp_suspend_cache_invalidation( true );
     356        $slug = __FUNCTION__;
     357        $t    = self::factory()->term->create(
     358            array(
     359                'slug'     => $slug,
     360                'taxonomy' => 'wptests_tax',
     361            )
     362        );
     363
     364        $this->assertEquals( $t, term_exists( $slug ) );
     365        $num_queries = $wpdb->num_queries;
     366        $this->assertEquals( $t, term_exists( $slug ) );
     367        $this->assertSame( $num_queries + 1, $wpdb->num_queries );
     368        wp_suspend_cache_invalidation( false );
     369
     370        // Clean up.
     371        _unregister_taxonomy( 'wptests_tax' );
     372    }
     373
     374    /**
     375     * @ticket 36949
     376     * @covers ::term_exists()
     377     */
     378    public function test_term_exists_caching_by_int_suspend_cache_invalidation() {
     379        register_taxonomy( 'wptests_tax', 'post' );
     380
     381        $slug = __FUNCTION__;
     382        $t    = self::factory()->term->create(
     383            array(
     384                'slug'     => $slug,
     385                'taxonomy' => 'wptests_tax',
     386            )
     387        );
     388
     389        // Warm cache in get_term() via term_exists().
     390        term_exists( $t );
     391        wp_suspend_cache_invalidation( true );
     392        wp_delete_term( $t, 'wptests_tax' );
     393        $this->assertNull( term_exists( $t ) );
     394
     395        // Reneable cache invalidation.
     396        wp_suspend_cache_invalidation( false );
     397        _unregister_taxonomy( 'wptests_tax' );
     398    }
     399
    274400    public function test_term_exists_unknown() {
    275401        $this->assertNull( term_exists( rand_str() ) );
  • trunk/tests/phpunit/tests/term/wpGetObjectTerms.php

    r52010 r52921  
    448448        $wpdb->update( $wpdb->term_taxonomy, array( 'term_taxonomy_id' => 100005 ), array( 'term_taxonomy_id' => $term_3->term_taxonomy_id ) );
    449449
     450        clean_term_cache( array( $t1, $t2, $t3 ), $this->taxonomy );
     451
    450452        $set = wp_set_object_terms( $p, array( $t1, $t2, $t3 ), $this->taxonomy );
    451453
Note: See TracChangeset for help on using the changeset viewer.