Make WordPress Core

Changeset 29940


Ignore:
Timestamp:
10/17/2014 08:20:04 PM (10 years ago)
Author:
boonebgorges
Message:

Overhaul SQL generating logic in WP_Meta_Query to avoid unnecessary table joins.

The logic used to generate clause SQL in WP_Meta_Query is somewhat arcane,
stemming mostly from an ongoing effort to eliminate costly table joins when
they are not necessary. By systematizing the process of looking for shareable
joins - as was done in WP_Tax_Query [29902] - it becomes possible to simplify
the construction of SQL queries in get_sql_for_clause(). Moreover, the
simplified logic is actually considerably better at identifying shareable
joins, such that certain uses of WP_Meta_Query will see joins reduced by 50%
or more.

Includes integration tests for a representative cross-section of the query
clause combinations that result in shared table aliases.

Props boonebgorges, sc0ttkclark.
See #24093.

Location:
trunk
Files:
3 edited

Legend:

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

    r29890 r29940  
    988988            // First-order clause.
    989989            } else if ( $this->is_first_order_clause( $query ) ) {
     990                if ( isset( $query['value'] ) && array() === $query['value'] ) {
     991                    unset( $query['value'] );
     992                }
     993
    990994                $clean_queries[] = $query;
    991995
     
    11681172     */
    11691173    protected function get_sql_clauses() {
    1170         $sql = $this->get_sql_for_query( $this->queries );
     1174        /*
     1175         * $queries are passed by reference to get_sql_for_query() for recursion.
     1176         * To keep $this->queries unaltered, pass a copy.
     1177         */
     1178        $queries = $this->queries;
     1179        $sql = $this->get_sql_for_query( $queries );
    11711180
    11721181        if ( ! empty( $sql['where'] ) ) {
     
    11961205     * }
    11971206     */
    1198     protected function get_sql_for_query( $query, $depth = 0 ) {
     1207    protected function get_sql_for_query( &$query, $depth = 0 ) {
    11991208        $sql_chunks = array(
    12001209            'join'  => array(),
     
    12121221        }
    12131222
    1214         foreach ( $query as $key => $clause ) {
     1223        foreach ( $query as $key => &$clause ) {
    12151224            if ( 'relation' === $key ) {
    12161225                $relation = $query['relation'];
     
    12791288     * }
    12801289     */
    1281     public function get_sql_for_clause( $clause, $parent_query ) {
     1290    public function get_sql_for_clause( &$clause, $parent_query ) {
    12821291        global $wpdb;
    12831292
     
    12871296        );
    12881297
    1289         $i = count( $this->table_aliases );
    1290         $alias = $i ? 'mt' . $i : $this->meta_table;
    1291 
    12921298        if ( isset( $clause['compare'] ) ) {
    1293             $meta_compare = strtoupper( $clause['compare'] );
     1299            $clause['compare'] = strtoupper( $clause['compare'] );
    12941300        } else {
    1295             $meta_compare = isset( $clause['value'] ) && is_array( $clause['value'] ) ? 'IN' : '=';
    1296         }
    1297 
    1298         if ( ! in_array( $meta_compare, array(
     1301            $clause['compare'] = isset( $clause['value'] ) && is_array( $clause['value'] ) ? 'IN' : '=';
     1302        }
     1303
     1304        if ( ! in_array( $clause['compare'], array(
    12991305            '=', '!=', '>', '>=', '<', '<=',
    13001306            'LIKE', 'NOT LIKE',
     
    13041310            'REGEXP', 'NOT REGEXP', 'RLIKE'
    13051311        ) ) ) {
    1306             $meta_compare = '=';
    1307         }
    1308 
    1309         /*
    1310          * There are a number of different query structures that get
    1311          * built in different ways.
    1312          * 1. Key-only clauses - (a) clauses without a 'value' key that
    1313          *    appear in the context of an OR relation and do not use
    1314          *    'NOT EXISTS' as the 'compare', or (b) clauses with an
    1315          *    empty array for 'value'.
    1316          */
    1317         if ( ! empty( $clause['key'] ) && (
    1318             ( ! array_key_exists( 'value', $clause ) && 'NOT EXISTS' !== $meta_compare && 'OR' === $parent_query['relation'] ) ||
    1319             ( isset( $clause['value'] ) && is_array( $clause['value'] ) && empty( $clause['value'] ) )
    1320         ) ) {
    1321 
    1322             $alias = $this->meta_table;
    1323             $sql_chunks['join'][] = " INNER JOIN $this->meta_table ON ($this->primary_table.$this->primary_id_column = $alias.$this->meta_id_column)";
    1324             $sql_chunks['where'][] = $wpdb->prepare( "$this->meta_table.meta_key = %s", trim( $clause['key'] ) );
    1325 
    1326         // 2. NOT EXISTS.
    1327         } else if ( 'NOT EXISTS' === $meta_compare ) {
    1328             $join  = " LEFT JOIN $this->meta_table";
    1329             $join .= $i ? " AS $alias" : '';
    1330             $join .= $wpdb->prepare( " ON ($this->primary_table.$this->primary_id_column = $alias.$this->meta_id_column AND $alias.meta_key = %s )", $clause['key'] );
     1312            $clause['compare'] = '=';
     1313        }
     1314
     1315        $meta_compare = $clause['compare'];
     1316
     1317        // First build the JOIN clause, if one is required.
     1318        $join = '';
     1319
     1320        // We prefer to avoid joins if possible. Look for an existing join compatible with this clause.
     1321        $alias = $this->find_compatible_table_alias( $clause, $parent_query );
     1322        if ( false === $alias ) {
     1323            $i = count( $this->table_aliases );
     1324            $alias = $i ? 'mt' . $i : $this->meta_table;
     1325
     1326            // JOIN clauses for NOT EXISTS have their own syntax.
     1327            if ( 'NOT EXISTS' === $meta_compare ) {
     1328                $join .= " LEFT JOIN $this->meta_table";
     1329                $join .= $i ? " AS $alias" : '';
     1330                $join .= $wpdb->prepare( " ON ($this->primary_table.$this->primary_id_column = $alias.$this->meta_id_column AND $alias.meta_key = %s )", $clause['key'] );
     1331                $sql_chunks['join'][] = $join;
     1332
     1333            // All other JOIN clauses.
     1334            } else {
     1335                $alias = $this->find_compatible_table_alias( $clause, $parent_query );
     1336                if ( false === $alias ) {
     1337                    $alias = $i ? 'mt' . $i : $this->meta_table;
     1338
     1339                    $join .= " INNER JOIN $this->meta_table";
     1340                    $join .= $i ? " AS $alias" : '';
     1341                    $join .= " ON ( $this->primary_table.$this->primary_id_column = $alias.$this->meta_id_column )";
     1342                }
     1343            }
     1344
     1345            $this->table_aliases[] = $alias;
    13311346            $sql_chunks['join'][] = $join;
    1332 
    1333             $sql_chunks['where'][] = $alias . '.' . $this->meta_id_column . ' IS NULL';
    1334 
    1335         // 3. EXISTS and other key-only queries.
    1336         } else if ( 'EXISTS' === $meta_compare || ( ! empty( $clause['key'] ) && ! array_key_exists( 'value', $clause ) ) ) {
    1337             $join  = " INNER JOIN $this->meta_table";
    1338             $join .= $i ? " AS $alias" : '';
    1339             $join .= " ON ( $this->primary_table.$this->primary_id_column = $alias.$this->meta_id_column )";
    1340             $sql_chunks['join'][] = $join;
    1341 
    1342             $sql_chunks['where'][] = $wpdb->prepare( $alias . '.meta_key = %s', trim( $clause['key'] ) );
    1343 
    1344         // 4. Clauses that have a value.
    1345         } else if ( array_key_exists( 'value', $clause ) ) {
    1346             $join  = " INNER JOIN $this->meta_table";
    1347             $join .= $i ? " AS $alias" : '';
    1348             $join .= " ON ($this->primary_table.$this->primary_id_column = $alias.$this->meta_id_column)";
    1349             $sql_chunks['join'][] = $join;
    1350 
    1351             if ( ! empty( $clause['key'] ) ) {
     1347        }
     1348
     1349        // Save the alias to this clause, for future siblings to find.
     1350        $clause['alias'] = $alias;
     1351
     1352        // Next, build the WHERE clause.
     1353        $where = '';
     1354
     1355        // meta_key.
     1356        if ( array_key_exists( 'key', $clause ) ) {
     1357            if ( 'NOT EXISTS' === $meta_compare ) {
     1358                $sql_chunks['where'][] = $alias . '.' . $this->meta_id_column . ' IS NULL';
     1359            } else {
    13521360                $sql_chunks['where'][] = $wpdb->prepare( "$alias.meta_key = %s", trim( $clause['key'] ) );
    13531361            }
    1354 
     1362        }
     1363
     1364        // meta_value.
     1365        if ( array_key_exists( 'value', $clause ) ) {
     1366            $meta_value = $clause['value'];
    13551367            $meta_type = $this->get_cast_for_type( isset( $clause['type'] ) ? $clause['type'] : '' );
    1356 
    1357             $meta_value = isset( $clause['value'] ) ? $clause['value'] : '';
    13581368
    13591369            if ( in_array( $meta_compare, array( 'IN', 'NOT IN', 'BETWEEN', 'NOT BETWEEN' ) ) ) {
     
    13651375            }
    13661376
    1367             if ( 'IN' == substr( $meta_compare, -2 ) ) {
    1368                 $meta_compare_string = '(' . substr( str_repeat( ',%s', count( $meta_value ) ), 1 ) . ')';
    1369             } elseif ( 'BETWEEN' == substr( $meta_compare, -7 ) ) {
    1370                 $meta_value = array_slice( $meta_value, 0, 2 );
    1371                 $meta_compare_string = '%s AND %s';
    1372             } elseif ( 'LIKE' == $meta_compare || 'NOT LIKE' == $meta_compare ) {
    1373                 $meta_value = '%' . $wpdb->esc_like( $meta_value ) . '%';
    1374                 $meta_compare_string = '%s';
    1375             } else {
    1376                 $meta_compare_string = '%s';
     1377            switch ( $meta_compare ) {
     1378                case 'IN' :
     1379                case 'NOT IN' :
     1380                    $meta_compare_string = '(' . substr( str_repeat( ',%s', count( $meta_value ) ), 1 ) . ')';
     1381                    $where = $wpdb->prepare( $meta_compare_string, $meta_value );
     1382                    break;
     1383
     1384                case 'BETWEEN' :
     1385                case 'NOT BETWEEN' :
     1386                    $meta_value = array_slice( $meta_value, 0, 2 );
     1387                    $where = $wpdb->prepare( '%s AND %s', $meta_value );
     1388                    break;
     1389
     1390                case 'LIKE' :
     1391                case 'NOT LIKE' :
     1392                    $meta_value = '%' . $wpdb->esc_like( $meta_value ) . '%';
     1393                    $where = $wpdb->prepare( '%s', $meta_value );
     1394                    break;
     1395
     1396                default :
     1397                    $where = $wpdb->prepare( '%s', $meta_value );
     1398                    break;
     1399
    13771400            }
    13781401
    1379             $sql_chunks['where'][] = $wpdb->prepare( "CAST($alias.meta_value AS {$meta_type}) {$meta_compare} {$meta_compare_string}", $meta_value );
     1402            if ( $where ) {
     1403                $sql_chunks['where'][] = "CAST($alias.meta_value AS {$meta_type}) {$meta_compare} {$where}";
     1404            }
    13801405        }
    13811406
     
    13881413        }
    13891414
    1390         $this->table_aliases[] = $alias;
    1391 
    13921415        return $sql_chunks;
     1416    }
     1417
     1418    /**
     1419     * Identify an existing table alias that is compatible with the current query clause.
     1420     *
     1421     * We avoid unnecessary table joins by allowing each clause to look for
     1422     * an existing table alias that is compatible with the query that it
     1423     * needs to perform. An existing alias is compatible if (a) it is a
     1424     * sibling of $clause (ie, it's under the scope of the same relation),
     1425     * and (b) the combination of operator and relation between the clauses
     1426     * allows for a shared table join. In the case of WP_Meta_Query, this
     1427     * only applies to IN clauses that are connected by the relation OR.
     1428     *
     1429     * @since 4.1.0
     1430     * @access protected
     1431     *
     1432     * @param  array       $clause       Query clause.
     1433     * @param  array       $parent_query Parent query of $clause.
     1434     * @return string|bool Table alias if found, otherwise false.
     1435     */
     1436    protected function find_compatible_table_alias( $clause, $parent_query ) {
     1437        $alias = false;
     1438
     1439        foreach ( $parent_query as $sibling ) {
     1440            // If the sibling has no alias yet, there's nothing to check.
     1441            if ( empty( $sibling['alias'] ) ) {
     1442                continue;
     1443            }
     1444
     1445            // We're only interested in siblings that are first-order clauses.
     1446            if ( ! is_array( $sibling ) || ! $this->is_first_order_clause( $sibling ) ) {
     1447                continue;
     1448            }
     1449
     1450            $compatible_compares = array();
     1451
     1452            // Clauses connected by OR can share joins as long as they have "positive" operators.
     1453            if ( 'OR' === $parent_query['relation'] ) {
     1454                $compatible_compares = array( '=', 'IN', 'BETWEEN', 'LIKE', 'REGEXP', 'RLIKE', '>', '>=', '<', '<=' );
     1455
     1456            // Clauses joined by AND with "negative" operators share a join only if they also share a key.
     1457            } else if ( isset( $sibling['key'] ) && isset( $clause['key'] ) && $sibling['key'] === $clause['key'] ) {
     1458                $compatible_compares = array( '!=', 'NOT IN', 'NOT LIKE' );
     1459            }
     1460
     1461            $clause_compare  = strtoupper( $clause['compare'] );
     1462            $sibling_compare = strtoupper( $sibling['compare'] );
     1463            if ( in_array( $clause_compare, $compatible_compares ) && in_array( $sibling_compare, $compatible_compares ) ) {
     1464                $alias = $sibling['alias'];
     1465                break;
     1466            }
     1467        }
     1468
     1469        return apply_filters( 'meta_query_find_compatible_table_alias', $alias, $clause, $parent_query, $this ) ;
    13931470    }
    13941471}
  • trunk/tests/phpunit/tests/meta/query.php

    r29887 r29940  
    7474        $sql = $query->get_sql( 'post', $wpdb->posts, 'ID' );
    7575
    76         $this->assertEquals( 2, substr_count( $sql['join'], 'INNER JOIN' ) );
     76        $this->assertEquals( 1, substr_count( $sql['join'], 'INNER JOIN' ) );
    7777    }
    7878
     
    477477        $sql = $query->get_sql( 'post', $wpdb->posts, 'ID', $this );
    478478
    479         // We should have 2 joins - one for my_first_key and one for my_second_key
    480         $this->assertEquals( 2, substr_count( $sql['join'], 'JOIN' ) );
    481 
    482         // The WHERE should check my_third_key against an unaliased table
    483         $this->assertEquals( 1, substr_count( $sql['where'], "$wpdb->postmeta.meta_key = 'my_third_key'" ) );
    484 
     479        $this->assertEquals( 3, substr_count( $sql['join'], 'JOIN' ) );
    485480    }
    486481
     
    547542        $this->assertSame( 0, substr_count( $sql['where'], "$wpdb->postmeta.meta_key = 'baz'" ) );
    548543
    549         // When a value exists, it's not a key-only query
    550         $this->assertSame( 0, substr_count( $sql['where'], "$wpdb->postmeta.meta_key = 'barry'" ) );
    551 
    552544        // 'AND' queries don't have key-only queries
    553545        $query2 = new WP_Meta_Query( array(
  • trunk/tests/phpunit/tests/post/query.php

    r29902 r29940  
    648648
    649649    /**
     650     * @ticket 24093
     651     */
     652    public function test_meta_query_relation_or_compare_equals() {
     653        $posts = $this->factory->post->create_many( 4 );
     654        add_post_meta( $posts[0], 'color', 'orange' );
     655        add_post_meta( $posts[1], 'color', 'blue' );
     656        add_post_meta( $posts[1], 'vegetable', 'onion' );
     657        add_post_meta( $posts[2], 'vegetable', 'shallot' );
     658
     659        $query = new WP_Query( array(
     660            'meta_query' => array(
     661                'relation' => 'OR',
     662                array(
     663                    'key' => 'vegetable',
     664                    'value' => 'onion',
     665                    'compare' => '=',
     666                ),
     667                array(
     668                    'key' => 'vegetable',
     669                    'value' => 'shallot',
     670                    'compare' => '=',
     671                ),
     672            ),
     673            'update_post_meta_cache' => false,
     674            'update_post_term_cache' => false,
     675            'fields' => 'ids',
     676        ) );
     677
     678        $expected = array( $posts[1], $posts[2] );
     679        $this->assertEqualSets( $expected, $query->posts );
     680    }
     681
     682    /**
     683     * @ticket 24093
     684     */
     685    public function test_meta_query_relation_or_compare_equals_different_keys() {
     686        $posts = $this->factory->post->create_many( 4 );
     687        add_post_meta( $posts[0], 'color', 'orange' );
     688        add_post_meta( $posts[1], 'color', 'blue' );
     689        add_post_meta( $posts[1], 'vegetable', 'onion' );
     690        add_post_meta( $posts[2], 'vegetable', 'shallot' );
     691
     692        $query = new WP_Query( array(
     693            'meta_query' => array(
     694                'relation' => 'OR',
     695                array(
     696                    'key' => 'vegetable',
     697                    'value' => 'onion',
     698                    'compare' => '=',
     699                ),
     700                array(
     701                    'key' => 'color',
     702                    'value' => 'orange',
     703                    'compare' => '=',
     704                ),
     705            ),
     706            'update_post_meta_cache' => false,
     707            'update_post_term_cache' => false,
     708            'fields' => 'ids',
     709        ) );
     710
     711        $expected = array( $posts[0], $posts[1] );
     712        $this->assertEqualSets( $expected, $query->posts );
     713    }
     714
     715    /**
     716     * @ticket 24093
     717     */
     718    public function test_meta_query_relation_or_compare_equals_and_in() {
     719        $posts = $this->factory->post->create_many( 4 );
     720        add_post_meta( $posts[0], 'color', 'orange' );
     721        add_post_meta( $posts[1], 'color', 'blue' );
     722        add_post_meta( $posts[1], 'vegetable', 'onion' );
     723        add_post_meta( $posts[2], 'vegetable', 'shallot' );
     724
     725        $query = new WP_Query( array(
     726            'meta_query' => array(
     727                'relation' => 'OR',
     728                array(
     729                    'key' => 'vegetable',
     730                    'value' => 'onion',
     731                    'compare' => '=',
     732                ),
     733                array(
     734                    'key' => 'color',
     735                    'value' => array( 'orange', 'green' ),
     736                    'compare' => 'IN',
     737                ),
     738            ),
     739            'update_post_meta_cache' => false,
     740            'update_post_term_cache' => false,
     741            'fields' => 'ids',
     742        ) );
     743
     744        $expected = array( $posts[0], $posts[1] );
     745        $this->assertEqualSets( $expected, $query->posts );
     746    }
     747
     748    /**
     749     * @ticket 24093
     750     */
     751    public function test_meta_query_relation_or_compare_equals_and_like() {
     752        $posts = $this->factory->post->create_many( 4 );
     753        add_post_meta( $posts[0], 'color', 'orange' );
     754        add_post_meta( $posts[1], 'color', 'blue' );
     755        add_post_meta( $posts[1], 'vegetable', 'onion' );
     756        add_post_meta( $posts[2], 'vegetable', 'shallot' );
     757
     758        $query = new WP_Query( array(
     759            'meta_query' => array(
     760                'relation' => 'OR',
     761                array(
     762                    'key' => 'vegetable',
     763                    'value' => 'onion',
     764                    'compare' => '=',
     765                ),
     766                array(
     767                    'key' => 'vegetable',
     768                    'value' => 'hall',
     769                    'compare' => 'LIKE',
     770                ),
     771            ),
     772            'update_post_meta_cache' => false,
     773            'update_post_term_cache' => false,
     774            'fields' => 'ids',
     775        ) );
     776
     777        $expected = array( $posts[1], $posts[2] );
     778        $this->assertEqualSets( $expected, $query->posts );
     779    }
     780
     781    /**
     782     * @ticket 24093
     783     */
     784    public function test_meta_query_relation_or_compare_equals_and_between() {
     785        $posts = $this->factory->post->create_many( 4 );
     786        add_post_meta( $posts[0], 'number_of_colors', '2' );
     787        add_post_meta( $posts[1], 'number_of_colors', '5' );
     788        add_post_meta( $posts[1], 'vegetable', 'onion' );
     789        add_post_meta( $posts[2], 'vegetable', 'shallot' );
     790
     791        $query = new WP_Query( array(
     792            'meta_query' => array(
     793                'relation' => 'OR',
     794                array(
     795                    'key' => 'vegetable',
     796                    'value' => 'shallot',
     797                    'compare' => '=',
     798                ),
     799                array(
     800                    'key' => 'number_of_colors',
     801                    'value' => array( 1, 3 ),
     802                    'compare' => 'BETWEEN',
     803                    'type' => 'SIGNED',
     804                ),
     805            ),
     806            'update_post_meta_cache' => false,
     807            'update_post_term_cache' => false,
     808            'fields' => 'ids',
     809        ) );
     810
     811        $expected = array( $posts[0], $posts[2] );
     812        $this->assertEqualSets( $expected, $query->posts );
     813    }
     814
     815    /**
     816     * @ticket 24093
     817     */
     818    public function test_meta_query_relation_and_compare_in_same_keys() {
     819        $posts = $this->factory->post->create_many( 4 );
     820        add_post_meta( $posts[0], 'color', 'orange' );
     821        add_post_meta( $posts[1], 'color', 'blue' );
     822        add_post_meta( $posts[1], 'vegetable', 'onion' );
     823        add_post_meta( $posts[2], 'vegetable', 'shallot' );
     824        add_post_meta( $posts[3], 'vegetable', 'banana' );
     825        add_post_meta( $posts[3], 'vegetable', 'onion' );
     826
     827        $query = new WP_Query( array(
     828            'meta_query' => array(
     829                'relation' => 'AND',
     830                array(
     831                    'key' => 'vegetable',
     832                    'value' => array( 'onion', 'shallot' ),
     833                    'compare' => 'IN',
     834                ),
     835                array(
     836                    'key' => 'vegetable',
     837                    'value' => array( 'banana' ),
     838                    'compare' => 'IN',
     839                ),
     840            ),
     841            'update_post_meta_cache' => false,
     842            'update_post_term_cache' => false,
     843            'fields' => 'ids',
     844        ) );
     845
     846        $expected = array( $posts[3] );
     847        $this->assertEqualSets( $expected, $query->posts );
     848    }
     849
     850    /**
     851     * @ticket 24093
     852     */
     853    public function test_meta_query_relation_and_compare_in_different_keys() {
     854        $posts = $this->factory->post->create_many( 4 );
     855        add_post_meta( $posts[0], 'color', 'orange' );
     856        add_post_meta( $posts[1], 'color', 'blue' );
     857        add_post_meta( $posts[1], 'vegetable', 'onion' );
     858        add_post_meta( $posts[1], 'vegetable', 'shallot' );
     859        add_post_meta( $posts[2], 'vegetable', 'shallot' );
     860        add_post_meta( $posts[3], 'vegetable', 'banana' );
     861
     862        $query = new WP_Query( array(
     863            'meta_query' => array(
     864                'relation' => 'AND',
     865                array(
     866                    'key' => 'vegetable',
     867                    'value' => array( 'onion', 'shallot' ),
     868                    'compare' => 'IN',
     869                ),
     870                array(
     871                    'key' => 'color',
     872                    'value' => array( 'blue' ),
     873                    'compare' => 'IN',
     874                ),
     875            ),
     876            'update_post_meta_cache' => false,
     877            'update_post_term_cache' => false,
     878            'fields' => 'ids',
     879        ) );
     880
     881        $expected = array( $posts[1] );
     882        $this->assertEqualSets( $expected, $query->posts );
     883    }
     884
     885    /**
     886     * @ticket 24093
     887     */
     888    public function test_meta_query_relation_and_compare_not_equals() {
     889        $posts = $this->factory->post->create_many( 4 );
     890        add_post_meta( $posts[0], 'color', 'orange' );
     891        add_post_meta( $posts[1], 'color', 'blue' );
     892        add_post_meta( $posts[1], 'vegetable', 'onion' );
     893        add_post_meta( $posts[2], 'vegetable', 'shallot' );
     894        add_post_meta( $posts[3], 'vegetable', 'banana' );
     895
     896        $query = new WP_Query( array(
     897            'meta_query' => array(
     898                'relation' => 'AND',
     899                array(
     900                    'key' => 'vegetable',
     901                    'value' => 'onion',
     902                    'compare' => '!=',
     903                ),
     904                array(
     905                    'key' => 'vegetable',
     906                    'value' => 'shallot',
     907                    'compare' => '!=',
     908                ),
     909            ),
     910            'update_post_meta_cache' => false,
     911            'update_post_term_cache' => false,
     912            'fields' => 'ids',
     913        ) );
     914
     915        $expected = array( $posts[3] );
     916        $this->assertEqualSets( $expected, $query->posts );
     917    }
     918
     919    /**
     920     * @ticket 24093
     921     */
     922    public function test_meta_query_relation_and_compare_not_equals_different_keys() {
     923        $posts = $this->factory->post->create_many( 4 );
     924
     925        // !shallot, but orange.
     926        add_post_meta( $posts[0], 'color', 'orange' );
     927        add_post_meta( $posts[0], 'vegetable', 'onion' );
     928
     929        // !orange, but shallot.
     930        add_post_meta( $posts[1], 'color', 'blue' );
     931        add_post_meta( $posts[1], 'vegetable', 'shallot' );
     932
     933        // Neither.
     934        add_post_meta( $posts[2], 'color', 'blue' );
     935        add_post_meta( $posts[2], 'vegetable', 'onion' );
     936
     937        $query = new WP_Query( array(
     938            'meta_query' => array(
     939                'relation' => 'AND',
     940                array(
     941                    'key' => 'vegetable',
     942                    'value' => 'shallot',
     943                    'compare' => '!=',
     944                ),
     945                array(
     946                    'key' => 'color',
     947                    'value' => 'orange',
     948                    'compare' => '!=',
     949                ),
     950            ),
     951            'update_post_meta_cache' => false,
     952            'update_post_term_cache' => false,
     953            'fields' => 'ids',
     954        ) );
     955
     956        $expected = array( $posts[2] );
     957        $this->assertEqualSets( $expected, $query->posts );
     958    }
     959
     960    /**
     961     * @ticket 24093
     962     */
     963    public function test_meta_query_relation_and_compare_not_equals_not_in() {
     964        $posts = $this->factory->post->create_many( 4 );
     965        add_post_meta( $posts[0], 'color', 'orange' );
     966        add_post_meta( $posts[1], 'color', 'blue' );
     967        add_post_meta( $posts[1], 'vegetable', 'onion' );
     968        add_post_meta( $posts[2], 'vegetable', 'shallot' );
     969        add_post_meta( $posts[3], 'vegetable', 'banana' );
     970
     971        $query = new WP_Query( array(
     972            'meta_query' => array(
     973                'relation' => 'AND',
     974                array(
     975                    'key' => 'vegetable',
     976                    'value' => 'onion',
     977                    'compare' => '!=',
     978                ),
     979                array(
     980                    'key' => 'vegetable',
     981                    'value' => array( 'shallot' ),
     982                    'compare' => 'NOT IN',
     983                ),
     984            ),
     985            'update_post_meta_cache' => false,
     986            'update_post_term_cache' => false,
     987            'fields' => 'ids',
     988        ) );
     989
     990        $expected = array( $posts[3] );
     991        $this->assertEqualSets( $expected, $query->posts );
     992    }
     993
     994    /**
     995     * @ticket 24093
     996     */
     997    public function test_meta_query_relation_and_compare_not_equals_and_not_like() {
     998        $posts = $this->factory->post->create_many( 4 );
     999        add_post_meta( $posts[0], 'color', 'orange' );
     1000        add_post_meta( $posts[1], 'color', 'blue' );
     1001        add_post_meta( $posts[1], 'vegetable', 'onion' );
     1002        add_post_meta( $posts[2], 'vegetable', 'shallot' );
     1003        add_post_meta( $posts[3], 'vegetable', 'banana' );
     1004
     1005        $query = new WP_Query( array(
     1006            'meta_query' => array(
     1007                'relation' => 'AND',
     1008                array(
     1009                    'key' => 'vegetable',
     1010                    'value' => 'onion',
     1011                    'compare' => '!=',
     1012                ),
     1013                array(
     1014                    'key' => 'vegetable',
     1015                    'value' => 'hall',
     1016                    'compare' => 'NOT LIKE',
     1017                ),
     1018            ),
     1019            'update_post_meta_cache' => false,
     1020            'update_post_term_cache' => false,
     1021            'fields' => 'ids',
     1022        ) );
     1023
     1024        $expected = array( $posts[3] );
     1025        $this->assertEqualSets( $expected, $query->posts );
     1026    }
     1027
     1028    /**
    6501029     * @ticket 23033
    6511030     * @group meta
     
    10411420        add_post_meta( $post_id6, 'baz', 0 );
    10421421
    1043         $posts = get_posts( array( 'meta_key' => 'foo', 'meta_value' => '0' ) );
    1044         $this->assertEquals( 1, count ( $posts ) );
    1045         foreach ( $posts as $post ) {
     1422        $q = new WP_Query( array( 'meta_key' => 'foo', 'meta_value' => '0' ) );
     1423        $this->assertEquals( 1, count ( $q->posts ) );
     1424        foreach ( $q->posts as $post ) {
    10461425            $this->assertInstanceOf( 'WP_Post', $post );
    10471426            $this->assertEquals( 'raw', $post->filter );
    10481427        }
    1049         $this->assertEquals( $post_id, $posts[0]->ID );
     1428        $this->assertEquals( $post_id, $q->posts[0]->ID );
    10501429
    10511430        $posts = get_posts( array( 'meta_key' => 'bar', 'meta_value' => '0' ) );
Note: See TracChangeset for help on using the changeset viewer.