WordPress.org

Make WordPress Core

Ticket #29642: 29642.patch

File 29642.patch, 20.3 KB (added by boonebgorges, 5 years ago)
  • src/wp-includes/meta.php

    diff --git src/wp-includes/meta.php src/wp-includes/meta.php
    index e3c43cf..ba23352 100644
    class WP_Meta_Query { 
    882882        public $relation;
    883883
    884884        /**
     885         * Table aliases used in the query currently being built.
     886         *
     887         * @access protected
     888         * @var array
     889         */
     890        protected $table_aliases = array();
     891
     892        /**
     893         * Database table to query for the metadata.
     894         *
     895         * @access public
     896         * @var string
     897         */
     898        public $meta_table;
     899
     900        /**
     901         * Column in meta_table that represents the ID of the object the metadata belongs to.
     902         *
     903         * @access public
     904         * @var string
     905         */
     906        public $meta_id_column;
     907
     908        /**
     909         * Database table that where the metadata's objects are stored (eg $wpdb->users)
     910         *
     911         * @access public
     912         * @var string
     913         */
     914        public $primary_table;
     915
     916        /**
     917         * Column in primary_table that represents the ID of the object.
     918         *
     919         * @access public
     920         * @var string
     921         */
     922        public $primary_id_column;
     923
     924        /**
    885925         * Constructor
    886926         *
    887927         * @param array $meta_query (optional) A meta query
    class WP_Meta_Query { 
    896936                        $this->relation = 'AND';
    897937                }
    898938
    899                 $this->queries = array();
     939                $this->queries = self::clean_queries( $meta_query );
     940        }
    900941
    901                 foreach ( $meta_query as $key => $query ) {
    902                         if ( ! is_array( $query ) )
    903                                 continue;
    904942
    905                         $this->queries[] = $query;
     943
     944        /**
     945         * Ensure that the meta_query argument passed to the class constructor is well-formed.
     946         *
     947         * Eliminates empty items and ensures that a 'relation' is set.
     948         *
     949         * @param  array $queries
     950         * @return array
     951         */
     952        public static function clean_queries( $queries ) {
     953                $clean_queries = array();
     954
     955                if ( ! is_array( $queries ) ) {
     956                        return $clean_queries;
     957                }
     958
     959                foreach ( $queries as $key => $query ) {
     960                        if ( 'relation' === $key ) {
     961                                $relation = $query;
     962
     963                        // The presence of a 'key' or 'value' key indicates
     964                        // that this is a first-order clause
     965                        } else if ( isset( $query['key'] ) || isset( $query['value'] ) ) {
     966                                $clean_queries[] = $query;
     967
     968                        // Otherwise, it's a nested query
     969                        } else {
     970                                $cleaned_query = self::clean_queries( $query );
     971
     972                                if ( ! empty( $cleaned_query ) ) {
     973                                        $clean_queries[] = $cleaned_query;
     974                                }
     975                        }
    906976                }
     977
     978                if ( empty( $clean_queries ) ) {
     979                        return $clean_queries;
     980                }
     981
     982                // Sanitize the 'relation' key
     983                if ( isset( $relation ) && 'OR' === strtoupper( $relation ) ) {
     984                        $clean_queries['relation'] = 'OR';
     985
     986                // If there is only a single clause, call the relation 'OR'.
     987                // This value will not actually be used to join clauses, but it
     988                // simplifies the logic around combining key-only queries
     989                } else if ( 1 === count( $clean_queries ) ) {
     990                        $clean_queries['relation'] = 'OR';
     991                } else {
     992                        $clean_queries['relation'] = 'AND';
     993                }
     994
     995                return $clean_queries;
    907996        }
    908997
    909998        /**
    class WP_Meta_Query { 
    9581047        }
    9591048
    9601049        /**
    961          * Generates SQL clauses to be appended to a main query.
     1050         * Generate the SQL clauses for a query array.
    9621051         *
    963          * @since 3.2.0
    964          * @access public
     1052         * In the case of a nested query, this method recurses to generate
     1053         * properly nested SQL.
    9651054         *
    966          * @param string $type Type of meta
    967          * @param string $primary_table
    968          * @param string $primary_id_column
    969          * @param object $context (optional) The main query object
    970          * @return array( 'join' => $join_sql, 'where' => $where_sql )
     1055         * @param  array $query Query to parse.
     1056         * @param  int   $depth Optional. Number of tree levels deep we
     1057         *                     currently are. Used to calculate indentation.
     1058         * @return array Array of 'join' and 'where' clauses.
    9711059         */
    972         public function get_sql( $type, $primary_table, $primary_id_column, $context = null ) {
    973                 global $wpdb;
    974 
    975                 if ( ! $meta_table = _get_meta_table( $type ) )
    976                         return false;
    977 
    978                 $meta_id_column = sanitize_key( $type . '_id' );
    979 
    980                 $join = array();
    981                 $where = array();
    982 
    983                 $key_only_queries = array();
    984                 $queries = array();
     1060        public function get_sql_for_query( $query, $depth = 0 ) {
     1061                $sql_chunks = array(
     1062                        'join'  => array(),
     1063                        'where' => array(),
     1064                );
    9851065
    986                 // Split out the queries with empty arrays as value
    987                 foreach ( $this->queries as $k => $q ) {
    988                         if ( isset( $q['value'] ) && is_array( $q['value'] ) && empty( $q['value'] ) ) {
    989                                 $key_only_queries[$k] = $q;
    990                                 unset( $this->queries[$k] );
    991                         }
    992                 }
     1066                $sql = array(
     1067                        'join'  => '',
     1068                        'where' => '',
     1069                );
    9931070
    994                 // Split out the meta_key only queries (we can only do this for OR)
    995                 if ( 'OR' == $this->relation ) {
    996                         foreach ( $this->queries as $k => $q ) {
    997                                 if ( ( empty( $q['compare'] ) || 'NOT EXISTS' != $q['compare'] ) && ! array_key_exists( 'value', $q ) && ! empty( $q['key'] ) )
    998                                         $key_only_queries[$k] = $q;
    999                                 else
    1000                                         $queries[$k] = $q;
    1001                         }
    1002                 } else {
    1003                         $queries = $this->queries;
     1071                $indent = '';
     1072                for ( $i = 0; $i < $depth; $i++ ) {
     1073                        $indent .= "\t";
    10041074                }
    10051075
    1006                 // Specify all the meta_key only queries in one go
    1007                 if ( $key_only_queries ) {
    1008                         $join[]  = "INNER JOIN $meta_table ON $primary_table.$primary_id_column = $meta_table.$meta_id_column";
     1076                foreach ( $query as $key => $clause ) {
     1077                        if ( 'relation' === $key ) {
     1078                                $relation = $query['relation'];
     1079                        } else {
     1080                                // This is a subquery
     1081                                if ( isset( $clause['relation'] ) ) {
     1082                                        $clause_sql = $this->get_sql_for_query( $clause, $depth + 1 );
     1083
     1084                                        $sql_chunks['where'][] = $clause_sql['where'];
     1085                                        $sql_chunks['join'][]  = $clause_sql['join'];
     1086
     1087
     1088                                // This is a first-order clause
     1089                                } else {
     1090                                        $clause_sql = $this->get_sql_for_clause( $clause, $query );
     1091
     1092                                        $where_count = count( $clause_sql['where'] );
     1093                                        if ( ! $where_count ) {
     1094                                                $sql_chunks['where'][] = '';
     1095                                        } else if ( 1 === $where_count ) {
     1096                                                $sql_chunks['where'][] = $clause_sql['where'][0];
     1097                                        } else {
     1098                                                $sql_chunks['where'][] = '( ' . implode( ' AND ', $clause_sql['where'] ) . ' )';
     1099                                        }
     1100                                        $sql_chunks['join'] = array_merge( $sql_chunks['join'], $clause_sql['join'] );
     1101                                }
    10091102
    1010                         foreach ( $key_only_queries as $key => $q )
    1011                                 $where["key-only-$key"] = $wpdb->prepare( "$meta_table.meta_key = %s", trim( $q['key'] ) );
     1103                        }
    10121104                }
    10131105
    1014                 foreach ( $queries as $k => $q ) {
    1015                         $meta_key = isset( $q['key'] ) ? trim( $q['key'] ) : '';
    1016                         $meta_type = $this->get_cast_for_type( isset( $q['type'] ) ? $q['type'] : '' );
    1017 
    1018                         if ( array_key_exists( 'value', $q ) && is_null( $q['value'] ) )
    1019                                 $q['value'] = '';
     1106                $sql['join'] = implode( ' ', array_unique( $sql_chunks['join'] ) );
     1107                $sql['where'] = '( ' . "\n\t" . $indent . implode( ' ' . "\n\t" . $indent . $relation . ' ' . "\n\t" . $indent, $sql_chunks['where'] ) . "\n" . $indent . ')' . "\n";
    10201108
    1021                         $meta_value = isset( $q['value'] ) ? $q['value'] : null;
     1109                return $sql;
     1110        }
    10221111
    1023                         if ( isset( $q['compare'] ) )
    1024                                 $meta_compare = strtoupper( $q['compare'] );
    1025                         else
    1026                                 $meta_compare = is_array( $meta_value ) ? 'IN' : '=';
     1112        /**
     1113         * Generate SQL JOIN and WHERE clauses for a first-order query clause.
     1114         *
     1115         * "First-order" means that it's an array with a 'key' or 'value'.
     1116         *
     1117         * @param  array         $clause       Query clause.
     1118         * @param  WP_Meta_Query $parent_query Query object.
     1119         * @return array
     1120         */
     1121        public function get_sql_for_clause( $clause, $parent_query ) {
     1122                global $wpdb;
    10271123
    1028                         if ( ! in_array( $meta_compare, array(
    1029                                 '=', '!=', '>', '>=', '<', '<=',
    1030                                 'LIKE', 'NOT LIKE',
    1031                                 'IN', 'NOT IN',
    1032                                 'BETWEEN', 'NOT BETWEEN',
    1033                                 'NOT EXISTS',
    1034                                 'REGEXP', 'NOT REGEXP', 'RLIKE'
    1035                         ) ) )
    1036                                 $meta_compare = '=';
     1124                $sql_chunks = array(
     1125                        'where' => array(),
     1126                        'join' => array(),
     1127                );
    10371128
    1038                         $i = count( $join );
    1039                         $alias = $i ? 'mt' . $i : $meta_table;
     1129                $i = count( $this->table_aliases );
     1130                $alias = $i ? 'mt' . $i : $this->meta_table;
    10401131
    1041                         if ( 'NOT EXISTS' == $meta_compare ) {
    1042                                 $join[$i]  = "LEFT JOIN $meta_table";
    1043                                 $join[$i] .= $i ? " AS $alias" : '';
    1044                                 $join[$i] .= " ON ($primary_table.$primary_id_column = $alias.$meta_id_column AND $alias.meta_key = '$meta_key')";
     1132                if ( isset( $clause['compare'] ) ) {
     1133                        $meta_compare = strtoupper( $clause['compare'] );
     1134                } else {
     1135                        $meta_compare = isset( $clause['value'] ) && is_array( $clause['value'] ) ? 'IN' : '=';
     1136                }
    10451137
    1046                                 $where[$k] = ' ' . $alias . '.' . $meta_id_column . ' IS NULL';
     1138                if ( ! in_array( $meta_compare, array(
     1139                        '=', '!=', '>', '>=', '<', '<=',
     1140                        'LIKE', 'NOT LIKE',
     1141                        'IN', 'NOT IN',
     1142                        'BETWEEN', 'NOT BETWEEN',
     1143                        'EXISTS', 'NOT EXISTS',
     1144                        'REGEXP', 'NOT REGEXP', 'RLIKE'
     1145                ) ) ) {
     1146                        $meta_compare = '=';
     1147                }
    10471148
    1048                                 continue;
     1149                // There are a number of different query structures that get
     1150                // built in different ways
     1151                // 1. Key-only clauses - (a) clauses without a 'value' key that
     1152                //    appear in the context of an OR relation and do not use
     1153                //    'NOT EXISTS' as the 'compare', or (b) clauses with an
     1154                //    empty array for 'value'
     1155                if ( ! empty( $clause['key'] ) && (
     1156                        ( ! array_key_exists( 'value', $clause ) && 'NOT EXISTS' !== $meta_compare && 'OR' === $parent_query['relation'] ) ||
     1157                        ( isset( $clause['value'] ) && is_array( $clause['value'] ) && empty( $clause['value'] ) )
     1158                ) ) {
     1159
     1160                        $alias = $this->meta_table;
     1161                        $sql_chunks['join'][] = " INNER JOIN $this->meta_table ON ($this->primary_table.$this->primary_id_column = $alias.$this->meta_id_column)";
     1162                        $sql_chunks['where'][] = $wpdb->prepare( "$this->meta_table.meta_key = %s", trim( $clause['key'] ) );
     1163
     1164                // 2. NOT EXISTS
     1165                } else if ( 'NOT EXISTS' === $meta_compare ) {
     1166                        $join  = " LEFT JOIN $this->meta_table";
     1167                        $join .= $i ? " AS $alias" : '';
     1168                        $join .= $wpdb->prepare( " ON ($this->primary_table.$this->primary_id_column = $alias.$this->meta_id_column AND $alias.meta_key = %s )", $clause['key'] );
     1169                        $sql_chunks['join'][] = $join;
     1170
     1171                        $sql_chunks['where'][] = $alias . '.' . $this->meta_id_column . ' IS NULL';
     1172
     1173                // 3. EXISTS and other key-only queries
     1174                } else if ( 'EXISTS' === $meta_compare || ( ! empty( $clause['key'] ) && ! array_key_exists( 'value', $clause ) ) ) {
     1175                        $join  = " INNER JOIN $this->meta_table";
     1176                        $join .= $i ? " AS $alias" : '';
     1177                        $join .= " ON ( $this->primary_table.$this->primary_id_column = $alias.$this->meta_id_column )";
     1178                        $sql_chunks['join'][] = $join;
     1179
     1180                        $sql_chunks['where'][] = $wpdb->prepare( $alias . '.meta_key = %s', trim( $clause['key'] ) );
     1181
     1182                // 4. Clauses that have a value
     1183                } else if ( array_key_exists( 'value', $clause ) ) {
     1184                        $join  = " INNER JOIN $this->meta_table";
     1185                        $join .= $i ? " AS $alias" : '';
     1186                        $join .= " ON ($this->primary_table.$this->primary_id_column = $alias.$this->meta_id_column)";
     1187                        $sql_chunks['join'][] = $join;
     1188
     1189                        if ( ! empty( $clause['key'] ) ) {
     1190                                $sql_chunks['where'][] = $wpdb->prepare( "$alias.meta_key = %s", trim( $clause['key'] ) );
    10491191                        }
    10501192
    1051                         $join[$i]  = "INNER JOIN $meta_table";
    1052                         $join[$i] .= $i ? " AS $alias" : '';
    1053                         $join[$i] .= " ON ($primary_table.$primary_id_column = $alias.$meta_id_column)";
     1193                        $meta_type = $this->get_cast_for_type( isset( $clause['type'] ) ? $clause['type'] : '' );
    10541194
    1055                         $where[$k] = '';
    1056                         if ( !empty( $meta_key ) )
    1057                                 $where[$k] = $wpdb->prepare( "$alias.meta_key = %s", $meta_key );
    1058 
    1059                         if ( is_null( $meta_value ) ) {
    1060                                 if ( empty( $where[$k] ) )
    1061                                         unset( $join[$i] );
    1062                                 continue;
    1063                         }
     1195                        $meta_value = isset( $clause['value'] ) ? $clause['value'] : '';
    10641196
    10651197                        if ( in_array( $meta_compare, array( 'IN', 'NOT IN', 'BETWEEN', 'NOT BETWEEN' ) ) ) {
    1066                                 if ( ! is_array( $meta_value ) )
     1198                                if ( ! is_array( $meta_value ) ) {
    10671199                                        $meta_value = preg_split( '/[,\s]+/', $meta_value );
    1068 
    1069                                 if ( empty( $meta_value ) ) {
    1070                                         unset( $join[$i] );
    1071                                         continue;
    10721200                                }
    10731201                        } else {
    10741202                                $meta_value = trim( $meta_value );
    10751203                        }
    10761204
    1077                         if ( 'IN' == substr( $meta_compare, -2) ) {
     1205                        if ( 'IN' == substr( $meta_compare, -2 ) ) {
    10781206                                $meta_compare_string = '(' . substr( str_repeat( ',%s', count( $meta_value ) ), 1 ) . ')';
    1079                         } elseif ( 'BETWEEN' == substr( $meta_compare, -7) ) {
     1207                        } elseif ( 'BETWEEN' == substr( $meta_compare, -7 ) ) {
    10801208                                $meta_value = array_slice( $meta_value, 0, 2 );
    10811209                                $meta_compare_string = '%s AND %s';
    10821210                        } elseif ( 'LIKE' == $meta_compare || 'NOT LIKE' == $meta_compare ) {
    class WP_Meta_Query { 
    10861214                                $meta_compare_string = '%s';
    10871215                        }
    10881216
    1089                         if ( ! empty( $where[$k] ) )
    1090                                 $where[$k] .= ' AND ';
     1217                        $sql_chunks['where'][] = $wpdb->prepare( "CAST($alias.meta_value AS {$meta_type}) {$meta_compare} {$meta_compare_string}", $meta_value );
     1218                }
    10911219
    1092                         $where[$k] = ' (' . $where[$k] . $wpdb->prepare( "CAST($alias.meta_value AS {$meta_type}) {$meta_compare} {$meta_compare_string})", $meta_value );
     1220                // Multiple WHERE clauses (for meta_key and meta_value) should
     1221                // be joined in parentheses
     1222                if ( 1 < count( $sql_chunks['where'] ) ) {
     1223                        $sql_chunks['where'] = array( '( ' . implode( ' AND ', $sql_chunks['where'] ) . ' )' );
    10931224                }
    10941225
    1095                 $where = array_filter( $where );
     1226                $this->table_aliases[] = $alias;
    10961227
    1097                 if ( empty( $where ) )
    1098                         $where = '';
    1099                 else
    1100                         $where = ' AND (' . implode( "\n{$this->relation} ", $where ) . ' )';
     1228                return $sql_chunks;
     1229        }
     1230
     1231        /**
     1232         * Generates SQL clauses to be appended to a main query.
     1233         *
     1234         * @since 3.2.0
     1235         * @access public
     1236         *
     1237         * @param string $type Type of meta
     1238         * @param string $primary_table
     1239         * @param string $primary_id_column
     1240         * @param object $context (optional) The main query object
     1241         * @return array( 'join' => $join_sql, 'where' => $where_sql )
     1242         */
     1243        public function get_sql( $type, $primary_table, $primary_id_column, $context = null ) {
     1244                global $wpdb;
    11011245
    1102                 $join = implode( "\n", $join );
    1103                 if ( ! empty( $join ) )
    1104                         $join = ' ' . $join;
     1246                if ( ! $meta_table = _get_meta_table( $type ) ) {
     1247                        return false;
     1248                }
     1249
     1250                $this->meta_table     = $meta_table;
     1251                $this->meta_id_column = sanitize_key( $type . '_id' );
     1252
     1253                $this->primary_table     = $primary_table;
     1254                $this->primary_id_column = $primary_id_column;
     1255
     1256                $sql = $this->get_sql_for_query( $this->queries );
     1257
     1258                if ( ! empty( $sql['where'] ) ) {
     1259                        $sql['where'] = ' AND ' . "\n" . $sql['where'] . "\n";
     1260                }
    11051261
    11061262                /**
    11071263                 * Filter the meta query's generated SQL.
    class WP_Meta_Query { 
    11191275                 *     @type object $context           The main query object.
    11201276                 * }
    11211277                 */
    1122                 return apply_filters_ref_array( 'get_meta_sql', array( compact( 'join', 'where' ), $this->queries, $type, $primary_table, $primary_id_column, $context ) );
     1278                return apply_filters_ref_array( 'get_meta_sql', array( $sql, $this->queries, $type, $primary_table, $primary_id_column, $context ) );
    11231279        }
    11241280}
    11251281
  • new file tests/phpunit/tests/meta/nested.php

    diff --git tests/phpunit/tests/meta/nested.php tests/phpunit/tests/meta/nested.php
    new file mode 100644
    index 0000000..7e9524d
    - +  
     1<?php
     2
     3/**
     4 * @group meta
     5 */
     6class WP_Tests_Nested_Meta extends WP_UnitTestCase {
     7        public function test_clean_queries_single_query() {
     8                $expected = array(
     9                        'relation' => 'OR',
     10                        array(
     11                                'key' => 'foo',
     12                                'value' => 'bar',
     13                        ),
     14                );
     15
     16                $found = WP_Meta_Query::clean_queries( array(
     17                        array(
     18                                'key' => 'foo',
     19                                'value' => 'bar',
     20                        ),
     21                ) );
     22
     23                $this->assertEquals( $expected, $found );
     24        }
     25
     26        public function test_clean_queries_multiple_first_order_queries_relation_default() {
     27                $expected = array(
     28                        'relation' => 'AND',
     29                        array(
     30                                'key' => 'foo',
     31                                'value' => 'bar',
     32                        ),
     33                        array(
     34                                'key' => 'foo2',
     35                                'value' => 'bar2',
     36                        ),
     37                );
     38
     39                $found = WP_Meta_Query::clean_queries( array(
     40                        array(
     41                                'key' => 'foo',
     42                                'value' => 'bar',
     43                        ),
     44                        array(
     45                                'key' => 'foo2',
     46                                'value' => 'bar2',
     47                        ),
     48                ) );
     49
     50                $this->assertEquals( $expected, $found );
     51        }
     52
     53        public function test_clean_queries_multiple_first_order_queries_relation_or() {
     54                $expected = array(
     55                        'relation' => 'OR',
     56                        array(
     57                                'key' => 'foo',
     58                                'value' => 'bar',
     59                        ),
     60                        array(
     61                                'key' => 'foo2',
     62                                'value' => 'bar2',
     63                        ),
     64                );
     65
     66                $found = WP_Meta_Query::clean_queries( array(
     67                        'relation' => 'OR',
     68                        array(
     69                                'key' => 'foo',
     70                                'value' => 'bar',
     71                        ),
     72                        array(
     73                                'key' => 'foo2',
     74                                'value' => 'bar2',
     75                        ),
     76                ) );
     77
     78                $this->assertEquals( $expected, $found );
     79        }
     80
     81        public function test_clean_queries_multiple_first_order_queries_relation_or_lowercase() {
     82                $expected = array(
     83                        'relation' => 'OR',
     84                        array(
     85                                'key' => 'foo',
     86                                'value' => 'bar',
     87                        ),
     88                        array(
     89                                'key' => 'foo2',
     90                                'value' => 'bar2',
     91                        ),
     92                );
     93
     94                $found = WP_Meta_Query::clean_queries( array(
     95                        'relation' => 'or',
     96                        array(
     97                                'key' => 'foo',
     98                                'value' => 'bar',
     99                        ),
     100                        array(
     101                                'key' => 'foo2',
     102                                'value' => 'bar2',
     103                        ),
     104                ) );
     105
     106                $this->assertEquals( $expected, $found );
     107        }
     108
     109        public function test_clean_queries_multiple_first_order_queries_invalid_relation() {
     110                $expected = array(
     111                        'relation' => 'AND',
     112                        array(
     113                                'key' => 'foo',
     114                                'value' => 'bar',
     115                        ),
     116                        array(
     117                                'key' => 'foo2',
     118                                'value' => 'bar2',
     119                        ),
     120                );
     121
     122                $found = WP_Meta_Query::clean_queries( array(
     123                        'relation' => 'FOO',
     124                        array(
     125                                'key' => 'foo',
     126                                'value' => 'bar',
     127                        ),
     128                        array(
     129                                'key' => 'foo2',
     130                                'value' => 'bar2',
     131                        ),
     132                ) );
     133
     134                $this->assertEquals( $expected, $found );
     135        }
     136
     137        public function test_clean_queries_single_query_which_is_a_nested_query() {
     138                $expected = array(
     139                        'relation' => 'OR',
     140                        array(
     141                                'relation' => 'AND',
     142                                array(
     143                                        'key' => 'foo',
     144                                        'value' => 'bar',
     145                                ),
     146                                array(
     147                                        'key' => 'foo2',
     148                                        'value' => 'bar2',
     149                                ),
     150                        )
     151                );
     152
     153                $found = WP_Meta_Query::clean_queries( array(
     154                        array(
     155                                array(
     156                                        'key' => 'foo',
     157                                        'value' => 'bar',
     158                                ),
     159                                array(
     160                                        'key' => 'foo2',
     161                                        'value' => 'bar2',
     162                                ),
     163                        ),
     164                ) );
     165
     166                $this->assertEquals( $expected, $found );
     167        }
     168
     169        public function test_clean_queries_multiple_nested_queries() {
     170                $expected = array(
     171                        'relation' => 'OR',
     172                        array(
     173                                'relation' => 'AND',
     174                                array(
     175                                        'key' => 'foo',
     176                                        'value' => 'bar',
     177                                ),
     178                                array(
     179                                        'key' => 'foo2',
     180                                        'value' => 'bar2',
     181                                ),
     182                        ),
     183                        array(
     184                                'relation' => 'AND',
     185                                array(
     186                                        'key' => 'foo3',
     187                                        'value' => 'bar3',
     188                                ),
     189                                array(
     190                                        'key' => 'foo4',
     191                                        'value' => 'bar4',
     192                                ),
     193                        ),
     194                );
     195
     196                $found = WP_Meta_Query::clean_queries( array(
     197                        'relation' => 'OR',
     198                        array(
     199                                array(
     200                                        'key' => 'foo',
     201                                        'value' => 'bar',
     202                                ),
     203                                array(
     204                                        'key' => 'foo2',
     205                                        'value' => 'bar2',
     206                                ),
     207                        ),
     208                        array(
     209                                array(
     210                                        'key' => 'foo3',
     211                                        'value' => 'bar3',
     212                                ),
     213                                array(
     214                                        'key' => 'foo4',
     215                                        'value' => 'bar4',
     216                                ),
     217                        ),
     218                ) );
     219
     220                $this->assertEquals( $expected, $found );
     221        }
     222
     223        public function test_meta_query_nested() {
     224                $p1 = $this->factory->post->create();
     225                $p2 = $this->factory->post->create();
     226                $p3 = $this->factory->post->create();
     227
     228                add_post_meta( $p1, 'foo', 'bar' );
     229                add_post_meta( $p2, 'foo2', 'bar' );
     230                add_post_meta( $p3, 'foo2', 'bar' );
     231                add_post_meta( $p3, 'foo3', 'bar' );
     232
     233                $query = new WP_Query( array(
     234                        'update_post_meta_cache' => false,
     235                        'update_term_meta_cache' => false,
     236                        'fields' => 'ids',
     237                        'meta_query' => array(
     238                                'relation' => 'OR',
     239                                array(
     240                                        'key' => 'foo',
     241                                        'value' => 'bar',
     242                                ),
     243                                array(
     244                                        'relation' => 'AND',
     245                                        array(
     246                                                'key' => 'foo2',
     247                                                'value' => 'bar',
     248                                        ),
     249                                        array(
     250                                                'key' => 'foo3',
     251                                                'value' => 'bar',
     252                                        ),
     253                                ),
     254                        ),
     255                ) );
     256
     257                $expected = array( $p1, $p3 );
     258                $this->assertEqualSets( $expected, $query->posts );
     259        }
     260
     261        public function test_meta_query_nested_two_levels_deep() {
     262                $p1 = $this->factory->post->create();
     263                $p2 = $this->factory->post->create();
     264                $p3 = $this->factory->post->create();
     265
     266                add_post_meta( $p1, 'foo', 'bar' );
     267                add_post_meta( $p3, 'foo2', 'bar' );
     268                add_post_meta( $p3, 'foo3', 'bar' );
     269                add_post_meta( $p3, 'foo4', 'bar' );
     270
     271                $query = new WP_Query( array(
     272                        'update_post_meta_cache' => false,
     273                        'update_term_meta_cache' => false,
     274                        'fields' => 'ids',
     275                        'meta_query' => array(
     276                                'relation' => 'OR',
     277                                array(
     278                                        'key' => 'foo',
     279                                        'value' => 'bar',
     280                                ),
     281                                array(
     282                                        'relation' => 'OR',
     283                                        array(
     284                                                'key' => 'foo2',
     285                                                'value' => 'bar',
     286                                        ),
     287                                        array(
     288                                                'relation' => 'AND',
     289                                                array(
     290                                                        'key' => 'foo3',
     291                                                        'value' => 'bar',
     292                                                ),
     293                                                array(
     294                                                        'key' => 'foo4',
     295                                                        'value' => 'bar',
     296                                                ),
     297                                        ),
     298                                ),
     299                        ),
     300                ) );
     301
     302                $expected = array( $p1, $p3 );
     303                $this->assertEqualSets( $expected, $query->posts );
     304        }
     305}