WordPress.org

Make WordPress Core

Changeset 32713


Ignore:
Timestamp:
06/09/2015 05:41:35 PM (6 years ago)
Author:
boonebgorges
Message:

Avoid returning duplicate matches when using a meta query in WP_User_Query.

A meta_query containing an OR relation can result in the same record matching
multiple clauses, leading to duplicate results. The previous prevention against
duplicates [18178] #17582 became unreliable in 4.1 when WP_Meta_Query
introduced support for nested clauses. The current changeset adds a new method
WP_Meta_Query::has_or_relation() for checking whether an OR relation
appears anywhere in the query, and uses the new method in WP_User_Query to
enforce distinct results as necessary.

Props maxxsnake.
Fixes #32592.

Location:
trunk
Files:
4 edited

Legend:

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

    r32610 r32713  
    955955
    956956    /**
     957     * Whether the query contains any OR relations.
     958     *
     959     * @since 4.3.0
     960     * @access protected
     961     * @var bool
     962     */
     963    protected $has_or_relation = false;
     964
     965    /**
    957966     * Constructor.
    958967     *
     
    10471056        if ( isset( $relation ) && 'OR' === strtoupper( $relation ) ) {
    10481057            $clean_queries['relation'] = 'OR';
     1058            $this->has_or_relation = true;
    10491059
    10501060        /*
     
    15791589        return apply_filters( 'meta_query_find_compatible_table_alias', $alias, $clause, $parent_query, $this ) ;
    15801590    }
     1591
     1592    /**
     1593     * Check whether the current query has any OR relations.
     1594     *
     1595     * In some cases, the presence of an OR relation somewhere in the query will require the use of a DISTINCT or
     1596     * GROUP BY keyword in the SELECT clause. The current method can be used in these cases to determine whether
     1597     * such a clause is necessary.
     1598     *
     1599     * @since 4.3.0
     1600     *
     1601     * @return bool True if the query contains any OR relations, otherwise false.
     1602     */
     1603    public function has_or_relation() {
     1604        return $this->has_or_relation;
     1605    }
    15811606}
    15821607
  • trunk/src/wp-includes/user.php

    r32696 r32713  
    710710            $this->query_where .= $clauses['where'];
    711711
    712             if ( 'OR' == $this->meta_query->relation ) {
     712            if ( $this->meta_query->has_or_relation() ) {
    713713                $this->query_fields = 'DISTINCT ' . $this->query_fields;
    714714            }
  • trunk/tests/phpunit/tests/meta/query.php

    r29964 r32713  
    807807        $this->assertNotContains( "{$wpdb->postmeta}.post_id IS NULL", $sql['where'] );
    808808    }
     809
     810    /**
     811     * @group 32592
     812     */
     813    public function test_has_or_relation_should_return_false() {
     814        $q = new WP_Meta_Query( array(
     815            'relation' => 'AND',
     816            array(
     817                'key' => 'foo',
     818                'value' => 'bar',
     819            ),
     820            array(
     821                'relation' => 'AND',
     822                array(
     823                    'key' => 'foo1',
     824                    'value' => 'bar',
     825                ),
     826                array(
     827                    'key' => 'foo2',
     828                    'value' => 'bar',
     829                ),
     830            ),
     831        ) );
     832
     833        $this->assertFalse( $q->has_or_relation() );
     834    }
     835
     836    /**
     837     * @group 32592
     838     */
     839    public function test_has_or_relation_should_return_true_for_top_level_or() {
     840        $q = new WP_Meta_Query( array(
     841            'relation' => 'OR',
     842            array(
     843                'key' => 'foo',
     844                'value' => 'bar',
     845            ),
     846            array(
     847                'relation' => 'AND',
     848                array(
     849                    'key' => 'foo1',
     850                    'value' => 'bar',
     851                ),
     852                array(
     853                    'key' => 'foo2',
     854                    'value' => 'bar',
     855                ),
     856            ),
     857        ) );
     858
     859        $this->assertTrue( $q->has_or_relation() );
     860    }
     861
     862    /**
     863     * @group 32592
     864     */
     865    public function test_has_or_relation_should_return_true_for_nested_or() {
     866        $q = new WP_Meta_Query( array(
     867            'relation' => 'AND',
     868            array(
     869                'key' => 'foo',
     870                'value' => 'bar',
     871            ),
     872            array(
     873                'relation' => 'OR',
     874                array(
     875                    'key' => 'foo1',
     876                    'value' => 'bar',
     877                ),
     878                array(
     879                    'key' => 'foo2',
     880                    'value' => 'bar',
     881                ),
     882            ),
     883        ) );
     884
     885        $this->assertTrue( $q->has_or_relation() );
     886    }
    809887}
  • trunk/tests/phpunit/tests/user/query.php

    r32683 r32713  
    790790        $this->assertEqualSets( $expected, $found );
    791791    }
     792
     793    /**
     794     * @ticket 32592
     795     */
     796    public function test_top_level_or_meta_query_should_eliminate_duplicate_matches() {
     797        $users = $this->factory->user->create_many( 3 );
     798
     799        add_user_meta( $users[0], 'foo', 'bar' );
     800        add_user_meta( $users[1], 'foo', 'bar' );
     801        add_user_meta( $users[0], 'foo2', 'bar2' );
     802
     803        $q = new WP_User_Query( array(
     804            'meta_query' => array(
     805                'relation' => 'OR',
     806                array(
     807                    'key' => 'foo',
     808                    'value' => 'bar',
     809                ),
     810                array(
     811                    'key' => 'foo2',
     812                    'value' => 'bar2',
     813                ),
     814            ),
     815        ) );
     816
     817        $found = wp_list_pluck( $q->get_results(), 'ID' );
     818        $expected = array( $users[0], $users[1] );
     819
     820        $this->assertEqualSets( $expected, $found );
     821    }
     822
     823    /**
     824     * @ticket 32592
     825     */
     826    public function test_nested_or_meta_query_should_eliminate_duplicate_matches() {
     827        $users = $this->factory->user->create_many( 3 );
     828
     829        add_user_meta( $users[0], 'foo', 'bar' );
     830        add_user_meta( $users[1], 'foo', 'bar' );
     831        add_user_meta( $users[0], 'foo2', 'bar2' );
     832        add_user_meta( $users[1], 'foo3', 'bar3' );
     833
     834        $q = new WP_User_Query( array(
     835            'meta_query' => array(
     836                'relation' => 'AND',
     837                array(
     838                    'key' => 'foo',
     839                    'value' => 'bar',
     840                ),
     841                array(
     842                    'relation' => 'OR',
     843                    array(
     844                        'key' => 'foo',
     845                        'value' => 'bar',
     846                    ),
     847                    array(
     848                        'key' => 'foo2',
     849                        'value' => 'bar2',
     850                    ),
     851                ),
     852            ),
     853        ) );
     854
     855        $found = wp_list_pluck( $q->get_results(), 'ID' );
     856        $expected = array( $users[0], $users[1] );
     857
     858        $this->assertEqualSets( $expected, $found );
     859    }
    792860}
Note: See TracChangeset for help on using the changeset viewer.