WordPress.org

Make WordPress Core

Ticket #29642: 29642.2.patch

File 29642.2.patch, 27.8 KB (added by boonebgorges, 5 years ago)
  • new file src/wp-includes/class-wp-recursive-query.php

    diff --git src/wp-includes/class-wp-recursive-query.php src/wp-includes/class-wp-recursive-query.php
    new file mode 100644
    index 0000000..ca68ba8
    - +  
     1<?php
     2
     3/**
     4 * Base class for creating query classes that generate SQL fragments for filtering results based on recursive query params.
     5 *
     6 *
     7 *
     8 * @since 4.1.0
     9 */
     10abstract class WP_Recursive_Query {
     11
     12        /**
     13         * Query arguments passed to the constructor.
     14         *
     15         * @since 4.1.0
     16         * @access public
     17         * @var array
     18         */
     19        public $queries = array();
     20
     21        /**
     22         * A flat list of table aliases used in JOIN clauses.
     23         *
     24         * @since 4.1.0
     25         * @access protected
     26         * @var array
     27         */
     28        protected $table_aliases = array();
     29
     30        /**
     31         * Generate SQL clauses to be appended to a main query.
     32         *
     33         * Extending classes should call this method from within a publicly
     34         * accessible get_sql() method, and manipulate the SQL as necessary.
     35         * For example, {@link WP_Tax_Query::get_sql()} is merely a wrapper for
     36         * get_sql_clauses(), while {@link WP_Date_Query::get_sql()} discards
     37         * the empty 'join' clauses are discarded, and passes the 'where'
     38         * clause through apply_filters().
     39         *
     40         * @since 4.1.0
     41         * @access protected
     42         *
     43         * @param  string $primary_table
     44         * @param  string $primary_id_column
     45         * @return array
     46         */
     47        protected function get_sql_clauses() {
     48                $sql = $this->get_sql_for_query( $this->queries );
     49
     50                if ( ! empty( $sql['where'] ) ) {
     51                        $sql['where'] = ' AND ' . "\n" . $sql['where'] . "\n";
     52                }
     53
     54                return $sql;
     55        }
     56
     57        /**
     58         * Generate SQL clauses for a single query array.
     59         *
     60         * If nested subqueries are found, this method recurses the tree to
     61         * produce the properly nested SQL.
     62         *
     63         * Subclasses generally do not need to call this method. It is invoked
     64         * automatically from get_sql_clauses().
     65         *
     66         * @since 4.1.0
     67         * @access protected
     68         *
     69         * @param  array $query Query to parse.
     70         * @param  int   $depth Optional. Number of tree levels deep we
     71         *                      currently are. Used to calculate indentation.
     72         * @return array
     73         */
     74        protected function get_sql_for_query( $query, $depth = 0 ) {
     75                $sql_chunks = array(
     76                        'join'  => array(),
     77                        'where' => array(),
     78                );
     79
     80                $sql = array(
     81                        'join'  => '',
     82                        'where' => '',
     83                );
     84
     85                $indent = '';
     86                for ( $i = 0; $i < $depth; $i++ ) {
     87                        $indent .= "\t";
     88                }
     89
     90                foreach ( $query as $key => $clause ) {
     91                        if ( 'relation' === $key ) {
     92                                $relation = $query['relation'];
     93                        } else {
     94                                // This is a first-order clause
     95                                if ( $this->is_first_order_clause( $clause ) ) {
     96                                        $clause_sql = $this->get_sql_for_clause( $clause, $query );
     97
     98                                        $where_count = count( $clause_sql['where'] );
     99                                        if ( ! $where_count ) {
     100                                                $sql_chunks['where'][] = '';
     101                                        } else if ( 1 === $where_count ) {
     102                                                $sql_chunks['where'][] = $clause_sql['where'][0];
     103                                        } else {
     104                                                $sql_chunks['where'][] = '( ' . implode( ' AND ', $clause_sql['where'] ) . ' )';
     105                                        }
     106
     107                                        $sql_chunks['join'] = array_merge( $sql_chunks['join'], $clause_sql['join'] );
     108                                // This is a subquery
     109                                } else {
     110                                        $clause_sql = $this->get_sql_for_query( $clause, $depth + 1 );
     111
     112                                        $sql_chunks['where'][] = $clause_sql['where'];
     113                                        $sql_chunks['join'][]  = $clause_sql['join'];
     114
     115                                }
     116                        }
     117                }
     118
     119                // Filter empties
     120                $sql_chunks['join']  = array_filter( $sql_chunks['join'] );
     121                $sql_chunks['where'] = array_filter( $sql_chunks['where'] );
     122
     123                if ( empty( $relation ) ) {
     124                        $relation = 'AND';
     125                }
     126
     127                if ( ! empty( $sql_chunks['join'] ) ) {
     128                        $sql['join'] = implode( ' ', array_unique( $sql_chunks['join'] ) );
     129                }
     130
     131                if ( ! empty( $sql_chunks['where'] ) ) {
     132                        $sql['where'] = '( ' . "\n\t" . $indent . implode( ' ' . "\n\t" . $indent . $relation . ' ' . "\n\t" . $indent, $sql_chunks['where'] ) . "\n" . $indent . ')' . "\n";
     133                }
     134
     135                return $sql;
     136        }
     137
     138        /**
     139         * Generate JOIN and WHERE clauses for a first-order clause.
     140         *
     141         * Must be overridden in a subclass.
     142         *
     143         * @since 4.1.0
     144         * @access protected
     145         *
     146         * @param  array $clause       Array of arguments belonging to the clause.
     147         * @param  array $parent_query Parent query to which the clause belongs.
     148         * @return array {
     149         *     @type array $join  Array of subclauses for the JOIN statement.
     150         *     @type array $where Array of subclauses for the WHERE statement.
     151         * }
     152         */
     153        abstract protected function get_sql_for_clause( $clause, $parent_query );
     154
     155        /**
     156         * Determine whether a clause is first-order.
     157         *
     158         * Must be overridden in a subclass.
     159         *
     160         * @since 4.1.0
     161         * @access protected
     162         *
     163         * @param  array $q Clause to check.
     164         * @return bool
     165         */
     166        abstract protected function is_first_order_clause( $query );
     167}
  • src/wp-includes/meta.php

    diff --git src/wp-includes/meta.php src/wp-includes/meta.php
    index e3c43cf..3b452fc 100644
    function get_meta_sql( $meta_query, $type, $primary_table, $primary_id_column, $ 
    853853 *
    854854 * @since 3.2.0
    855855 */
    856 class WP_Meta_Query {
     856class WP_Meta_Query extends WP_Recursive_Query {
    857857        /**
    858858        * List of metadata queries. A single query is an associative array:
    859859        * - 'key' string The meta key
    class WP_Meta_Query { 
    882882        public $relation;
    883883
    884884        /**
    885          * Constructor
     885         * Database table to query for the metadata.
    886886         *
    887          * @param array $meta_query (optional) A meta query
     887         * @access public
     888         * @var string
     889         */
     890        public $meta_table;
     891
     892        /**
     893         * Column in meta_table that represents the ID of the object the metadata belongs to.
     894         *
     895         * @access public
     896         * @var string
     897         */
     898        public $meta_id_column;
     899
     900        /**
     901         * Database table that where the metadata's objects are stored (eg $wpdb->users).
     902         *
     903         * @access public
     904         * @var string
     905         */
     906        public $primary_table;
     907
     908        /**
     909         * Column in primary_table that represents the ID of the object.
     910         *
     911         * @access public
     912         * @var string
     913         */
     914        public $primary_id_column;
     915
     916        /**
     917         * Constructor.
     918         *
     919         * @param array $meta_query (optional) A meta query.
    888920         */
    889921        public function __construct( $meta_query = false ) {
    890922                if ( !$meta_query )
    class WP_Meta_Query { 
    896928                        $this->relation = 'AND';
    897929                }
    898930
    899                 $this->queries = array();
     931                $this->queries = $this->sanitize_query( $meta_query );
     932        }
     933
     934        /**
     935         * Ensure that the meta_query argument passed to the class constructor is well-formed.
     936         *
     937         * Eliminates empty items and ensures that a 'relation' is set.
     938         *
     939         * @param  array $queries
     940         * @return array
     941         */
     942        public function sanitize_query( $queries ) {
     943                $clean_queries = array();
     944
     945                if ( ! is_array( $queries ) ) {
     946                        return $clean_queries;
     947                }
     948
     949                foreach ( $queries as $key => $query ) {
     950                        if ( 'relation' === $key ) {
     951                                $relation = $query;
     952
     953                        // First-order clause.
     954                        } else if ( $this->is_first_order_clause( $query ) ) {
     955                                $clean_queries[] = $query;
     956
     957                        // Otherwise, it's a nested query.
     958                        } else {
     959                                $cleaned_query = $this->sanitize_query( $query );
     960
     961                                if ( ! empty( $cleaned_query ) ) {
     962                                        $clean_queries[] = $cleaned_query;
     963                                }
     964                        }
     965                }
     966
     967                if ( empty( $clean_queries ) ) {
     968                        return $clean_queries;
     969                }
     970
     971                // Sanitize the 'relation' key provided in the query.
     972                if ( isset( $relation ) && 'OR' === strtoupper( $relation ) ) {
     973                        $clean_queries['relation'] = 'OR';
    900974
    901                 foreach ( $meta_query as $key => $query ) {
    902                         if ( ! is_array( $query ) )
    903                                 continue;
     975                // If there is only a single clause, call the relation 'OR'.
     976                // This value will not actually be used to join clauses, but it
     977                // simplifies the logic around combining key-only queries.
     978                } else if ( 1 === count( $clean_queries ) ) {
     979                        $clean_queries['relation'] = 'OR';
    904980
    905                         $this->queries[] = $query;
     981                // Default to AND.
     982                } else {
     983                        $clean_queries['relation'] = 'AND';
    906984                }
     985
     986                return $clean_queries;
     987        }
     988
     989        protected function is_first_order_clause( $query ) {
     990                return isset( $query['key'] ) || isset( $query['value'] );
    907991        }
    908992
    909993        /**
    class WP_Meta_Query { 
    9581042        }
    9591043
    9601044        /**
    961          * Generates SQL clauses to be appended to a main query.
     1045         * Generate SQL JOIN and WHERE clauses for a first-order query clause.
    9621046         *
    963          * @since 3.2.0
    964          * @access public
     1047         * "First-order" means that it's an array with a 'key' or 'value'.
    9651048         *
    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 )
     1049         * @param  array $clause       Query clause.
     1050         * @param  array $parent_query Parent query array.
     1051         * @return array
    9711052         */
    972         public function get_sql( $type, $primary_table, $primary_id_column, $context = null ) {
     1053        public function get_sql_for_clause( $clause, $parent_query ) {
    9731054                global $wpdb;
    9741055
    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();
     1056                $sql_chunks = array(
     1057                        'where' => array(),
     1058                        'join' => array(),
     1059                );
    9821060
    983                 $key_only_queries = array();
    984                 $queries = array();
     1061                $i = count( $this->table_aliases );
     1062                $alias = $i ? 'mt' . $i : $this->meta_table;
    9851063
    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                 }
    993 
    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                         }
     1064                if ( isset( $clause['compare'] ) ) {
     1065                        $meta_compare = strtoupper( $clause['compare'] );
    10021066                } else {
    1003                         $queries = $this->queries;
     1067                        $meta_compare = isset( $clause['value'] ) && is_array( $clause['value'] ) ? 'IN' : '=';
    10041068                }
    10051069
    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";
    1009 
    1010                         foreach ( $key_only_queries as $key => $q )
    1011                                 $where["key-only-$key"] = $wpdb->prepare( "$meta_table.meta_key = %s", trim( $q['key'] ) );
     1070                if ( ! in_array( $meta_compare, array(
     1071                        '=', '!=', '>', '>=', '<', '<=',
     1072                        'LIKE', 'NOT LIKE',
     1073                        'IN', 'NOT IN',
     1074                        'BETWEEN', 'NOT BETWEEN',
     1075                        'EXISTS', 'NOT EXISTS',
     1076                        'REGEXP', 'NOT REGEXP', 'RLIKE'
     1077                ) ) ) {
     1078                        $meta_compare = '=';
    10121079                }
    10131080
    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'] = '';
    1020 
    1021                         $meta_value = isset( $q['value'] ) ? $q['value'] : null;
    1022 
    1023                         if ( isset( $q['compare'] ) )
    1024                                 $meta_compare = strtoupper( $q['compare'] );
    1025                         else
    1026                                 $meta_compare = is_array( $meta_value ) ? 'IN' : '=';
    1027 
    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 = '=';
    1037 
    1038                         $i = count( $join );
    1039                         $alias = $i ? 'mt' . $i : $meta_table;
    1040 
    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')";
    1045 
    1046                                 $where[$k] = ' ' . $alias . '.' . $meta_id_column . ' IS NULL';
    1047 
    1048                                 continue;
     1081                // There are a number of different query structures that get
     1082                // built in different ways.
     1083                // 1. Key-only clauses - (a) clauses without a 'value' key that
     1084                //    appear in the context of an OR relation and do not use
     1085                //    'NOT EXISTS' as the 'compare', or (b) clauses with an
     1086                //    empty array for 'value'.
     1087                if ( ! empty( $clause['key'] ) && (
     1088                        ( ! array_key_exists( 'value', $clause ) && 'NOT EXISTS' !== $meta_compare && 'OR' === $parent_query['relation'] ) ||
     1089                        ( isset( $clause['value'] ) && is_array( $clause['value'] ) && empty( $clause['value'] ) )
     1090                ) ) {
     1091
     1092                        $alias = $this->meta_table;
     1093                        $sql_chunks['join'][] = " INNER JOIN $this->meta_table ON ($this->primary_table.$this->primary_id_column = $alias.$this->meta_id_column)";
     1094                        $sql_chunks['where'][] = $wpdb->prepare( "$this->meta_table.meta_key = %s", trim( $clause['key'] ) );
     1095
     1096                // 2. NOT EXISTS.
     1097                } else if ( 'NOT EXISTS' === $meta_compare ) {
     1098                        $join  = " LEFT JOIN $this->meta_table";
     1099                        $join .= $i ? " AS $alias" : '';
     1100                        $join .= $wpdb->prepare( " ON ($this->primary_table.$this->primary_id_column = $alias.$this->meta_id_column AND $alias.meta_key = %s )", $clause['key'] );
     1101                        $sql_chunks['join'][] = $join;
     1102
     1103                        $sql_chunks['where'][] = $alias . '.' . $this->meta_id_column . ' IS NULL';
     1104
     1105                // 3. EXISTS and other key-only queries.
     1106                } else if ( 'EXISTS' === $meta_compare || ( ! empty( $clause['key'] ) && ! array_key_exists( 'value', $clause ) ) ) {
     1107                        $join  = " INNER JOIN $this->meta_table";
     1108                        $join .= $i ? " AS $alias" : '';
     1109                        $join .= " ON ( $this->primary_table.$this->primary_id_column = $alias.$this->meta_id_column )";
     1110                        $sql_chunks['join'][] = $join;
     1111
     1112                        $sql_chunks['where'][] = $wpdb->prepare( $alias . '.meta_key = %s', trim( $clause['key'] ) );
     1113
     1114                // 4. Clauses that have a value.
     1115                } else if ( array_key_exists( 'value', $clause ) ) {
     1116                        $join  = " INNER JOIN $this->meta_table";
     1117                        $join .= $i ? " AS $alias" : '';
     1118                        $join .= " ON ($this->primary_table.$this->primary_id_column = $alias.$this->meta_id_column)";
     1119                        $sql_chunks['join'][] = $join;
     1120
     1121                        if ( ! empty( $clause['key'] ) ) {
     1122                                $sql_chunks['where'][] = $wpdb->prepare( "$alias.meta_key = %s", trim( $clause['key'] ) );
    10491123                        }
    10501124
    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)";
    1054 
    1055                         $where[$k] = '';
    1056                         if ( !empty( $meta_key ) )
    1057                                 $where[$k] = $wpdb->prepare( "$alias.meta_key = %s", $meta_key );
     1125                        $meta_type = $this->get_cast_for_type( isset( $clause['type'] ) ? $clause['type'] : '' );
    10581126
    1059                         if ( is_null( $meta_value ) ) {
    1060                                 if ( empty( $where[$k] ) )
    1061                                         unset( $join[$i] );
    1062                                 continue;
    1063                         }
     1127                        $meta_value = isset( $clause['value'] ) ? $clause['value'] : '';
    10641128
    10651129                        if ( in_array( $meta_compare, array( 'IN', 'NOT IN', 'BETWEEN', 'NOT BETWEEN' ) ) ) {
    1066                                 if ( ! is_array( $meta_value ) )
     1130                                if ( ! is_array( $meta_value ) ) {
    10671131                                        $meta_value = preg_split( '/[,\s]+/', $meta_value );
    1068 
    1069                                 if ( empty( $meta_value ) ) {
    1070                                         unset( $join[$i] );
    1071                                         continue;
    10721132                                }
    10731133                        } else {
    10741134                                $meta_value = trim( $meta_value );
    10751135                        }
    10761136
    1077                         if ( 'IN' == substr( $meta_compare, -2) ) {
     1137                        if ( 'IN' == substr( $meta_compare, -2 ) ) {
    10781138                                $meta_compare_string = '(' . substr( str_repeat( ',%s', count( $meta_value ) ), 1 ) . ')';
    1079                         } elseif ( 'BETWEEN' == substr( $meta_compare, -7) ) {
     1139                        } elseif ( 'BETWEEN' == substr( $meta_compare, -7 ) ) {
    10801140                                $meta_value = array_slice( $meta_value, 0, 2 );
    10811141                                $meta_compare_string = '%s AND %s';
    10821142                        } elseif ( 'LIKE' == $meta_compare || 'NOT LIKE' == $meta_compare ) {
    class WP_Meta_Query { 
    10861146                                $meta_compare_string = '%s';
    10871147                        }
    10881148
    1089                         if ( ! empty( $where[$k] ) )
    1090                                 $where[$k] .= ' AND ';
     1149                        $sql_chunks['where'][] = $wpdb->prepare( "CAST($alias.meta_value AS {$meta_type}) {$meta_compare} {$meta_compare_string}", $meta_value );
     1150                }
    10911151
    1092                         $where[$k] = ' (' . $where[$k] . $wpdb->prepare( "CAST($alias.meta_value AS {$meta_type}) {$meta_compare} {$meta_compare_string})", $meta_value );
     1152                // Multiple WHERE clauses (for meta_key and meta_value) should
     1153                // be joined in parentheses.
     1154                if ( 1 < count( $sql_chunks['where'] ) ) {
     1155                        $sql_chunks['where'] = array( '( ' . implode( ' AND ', $sql_chunks['where'] ) . ' )' );
    10931156                }
    10941157
    1095                 $where = array_filter( $where );
     1158                $this->table_aliases[] = $alias;
    10961159
    1097                 if ( empty( $where ) )
    1098                         $where = '';
    1099                 else
    1100                         $where = ' AND (' . implode( "\n{$this->relation} ", $where ) . ' )';
     1160                return $sql_chunks;
     1161        }
     1162
     1163        /**
     1164         * Generates SQL clauses to be appended to a main query.
     1165         *
     1166         * @since 3.2.0
     1167         * @access public
     1168         *
     1169         * @param  string $type              Type of meta, eg 'user', 'post'.
     1170         * @param  string $primary_table     Database table where the object being
     1171         *                                   filtered is stored (eg wp_users).
     1172         * @param  string $primary_id_column ID column for the filtered object in $primary_table
     1173         * @param  object $context           Optional. The main query object.
     1174         * @return array {
     1175         *      @type string $join SQL fragment to append to the main JOIN clause.
     1176         *      @type string $where SQL fragment to append to the main WHERE clause.
     1177         * }
     1178         */
     1179        public function get_sql( $type, $primary_table, $primary_id_column, $context = null ) {
     1180                global $wpdb;
     1181
     1182                if ( ! $meta_table = _get_meta_table( $type ) ) {
     1183                        return false;
     1184                }
     1185
     1186                $this->meta_table     = $meta_table;
     1187                $this->meta_id_column = sanitize_key( $type . '_id' );
     1188
     1189                $this->primary_table     = $primary_table;
     1190                $this->primary_id_column = $primary_id_column;
    11011191
    1102                 $join = implode( "\n", $join );
    1103                 if ( ! empty( $join ) )
    1104                         $join = ' ' . $join;
     1192                $sql = $this->get_sql_clauses();
    11051193
    11061194                /**
    11071195                 * Filter the meta query's generated SQL.
    class WP_Meta_Query { 
    11191207                 *     @type object $context           The main query object.
    11201208                 * }
    11211209                 */
    1122                 return apply_filters_ref_array( 'get_meta_sql', array( compact( 'join', 'where' ), $this->queries, $type, $primary_table, $primary_id_column, $context ) );
     1210                return apply_filters_ref_array( 'get_meta_sql', array( $sql, $this->queries, $type, $primary_table, $primary_id_column, $context ) );
    11231211        }
    11241212}
    11251213
  • src/wp-settings.php

    diff --git src/wp-settings.php src/wp-settings.php
    index 9795971..5dca996 100644
    wp_not_installed(); 
    111111// Load most of WordPress.
    112112require( ABSPATH . WPINC . '/class-wp-walker.php' );
    113113require( ABSPATH . WPINC . '/class-wp-ajax-response.php' );
     114require( ABSPATH . WPINC . '/class-wp-recursive-query.php' );
    114115require( ABSPATH . WPINC . '/formatting.php' );
    115116require( ABSPATH . WPINC . '/capabilities.php' );
    116117require( ABSPATH . WPINC . '/query.php' );
  • tests/phpunit/tests/meta/query.php

    diff --git tests/phpunit/tests/meta/query.php tests/phpunit/tests/meta/query.php
    index fb2a1cc..390ccc9 100644
    class Tests_Meta_Query extends WP_UnitTestCase { 
    4040                        array(),
    4141                ) );
    4242
    43                 $this->assertSame( array( array() ), $query->queries );
     43                $this->assertSame( array(), $query->queries );
    4444        }
    4545
    4646        /**
    class Tests_Meta_Query extends WP_UnitTestCase { 
    153153                $query = new WP_Meta_Query();
    154154
    155155                // just meta_value
    156                 $query->parse_query_vars( array( 'meta_key' => 'abc' ) );
    157 
    158                 $this->assertEquals( array( array( 'key' => 'abc' ) ), $query->queries );
     156                $expected = array(
     157                        'relation' => 'OR',
     158                        array(
     159                                'key' => 'abc',
     160                        ),
     161                );
     162                $query->parse_query_vars( array(
     163                        'meta_key' => 'abc',
     164                ) );
     165                $this->assertEquals( $expected, $query->queries );
    159166
    160167                // meta_key & meta_value
    161                 $query->parse_query_vars( array( 'meta_key' => 'abc', 'meta_value' => 'def' ) );
    162 
    163                 $this->assertEquals( array( array( 'key' => 'abc', 'value' => 'def' ) ), $query->queries );
     168                $expected = array(
     169                        'relation' => 'OR',
     170                        array(
     171                                'key' => 'abc',
     172                                'value' => 'def',
     173                        ),
     174                );
     175                $query->parse_query_vars( array(
     176                        'meta_key' => 'abc',
     177                        'meta_value' => 'def',
     178                ) );
     179                $this->assertEquals( $expected, $query->queries );
    164180
    165181                // meta_compare
    166                 $query->parse_query_vars( array( 'meta_key' => 'abc', 'meta_compare' => '=>' ) );
    167 
    168                 $this->assertEquals( array( array( 'key' => 'abc', 'compare' => '=>' ) ), $query->queries );
     182                $expected = array(
     183                        'relation' => 'OR',
     184                        array(
     185                                'key' => 'abc',
     186                                'compare' => '=>',
     187                        ),
     188                );
     189                $query->parse_query_vars( array(
     190                        'meta_key' => 'abc',
     191                        'meta_compare' => '=>',
     192                ) );
     193                $this->assertEquals( $expected, $query->queries );
    169194        }
    170195
    171196        /**
    class Tests_Meta_Query extends WP_UnitTestCase { 
    202227                $this->assertEquals( 'CHAR', $query->get_cast_for_type( 'ANYTHING ELSE' ) );
    203228        }
    204229
     230        public function test_sanitize_query_single_query() {
     231                $expected = array(
     232                        'relation' => 'OR',
     233                        array(
     234                                'key' => 'foo',
     235                                'value' => 'bar',
     236                        ),
     237                );
     238
     239                $q = new WP_Meta_Query();
     240                $found = $q->sanitize_query( array(
     241                        array(
     242                                'key' => 'foo',
     243                                'value' => 'bar',
     244                        ),
     245                ) );
     246
     247                $this->assertEquals( $expected, $found );
     248        }
     249
     250        public function test_sanitize_query_multiple_first_order_queries_relation_default() {
     251                $expected = array(
     252                        'relation' => 'AND',
     253                        array(
     254                                'key' => 'foo',
     255                                'value' => 'bar',
     256                        ),
     257                        array(
     258                                'key' => 'foo2',
     259                                'value' => 'bar2',
     260                        ),
     261                );
     262
     263                $q = new WP_Meta_Query();
     264                $found = $q->sanitize_query( array(
     265                        array(
     266                                'key' => 'foo',
     267                                'value' => 'bar',
     268                        ),
     269                        array(
     270                                'key' => 'foo2',
     271                                'value' => 'bar2',
     272                        ),
     273                ) );
     274
     275                $this->assertEquals( $expected, $found );
     276        }
     277
     278        public function test_sanitize_query_multiple_first_order_queries_relation_or() {
     279                $expected = array(
     280                        'relation' => 'OR',
     281                        array(
     282                                'key' => 'foo',
     283                                'value' => 'bar',
     284                        ),
     285                        array(
     286                                'key' => 'foo2',
     287                                'value' => 'bar2',
     288                        ),
     289                );
     290
     291                $q = new WP_Meta_Query();
     292                $found = $q->sanitize_query( array(
     293                        'relation' => 'OR',
     294                        array(
     295                                'key' => 'foo',
     296                                'value' => 'bar',
     297                        ),
     298                        array(
     299                                'key' => 'foo2',
     300                                'value' => 'bar2',
     301                        ),
     302                ) );
     303
     304                $this->assertEquals( $expected, $found );
     305        }
     306
     307        public function test_sanitize_query_multiple_first_order_queries_relation_or_lowercase() {
     308                $expected = array(
     309                        'relation' => 'OR',
     310                        array(
     311                                'key' => 'foo',
     312                                'value' => 'bar',
     313                        ),
     314                        array(
     315                                'key' => 'foo2',
     316                                'value' => 'bar2',
     317                        ),
     318                );
     319
     320                $q = new WP_Meta_Query();
     321                $found = $q->sanitize_query( array(
     322                        'relation' => 'or',
     323                        array(
     324                                'key' => 'foo',
     325                                'value' => 'bar',
     326                        ),
     327                        array(
     328                                'key' => 'foo2',
     329                                'value' => 'bar2',
     330                        ),
     331                ) );
     332
     333                $this->assertEquals( $expected, $found );
     334        }
     335
     336        public function test_sanitize_query_multiple_first_order_queries_invalid_relation() {
     337                $expected = array(
     338                        'relation' => 'AND',
     339                        array(
     340                                'key' => 'foo',
     341                                'value' => 'bar',
     342                        ),
     343                        array(
     344                                'key' => 'foo2',
     345                                'value' => 'bar2',
     346                        ),
     347                );
     348
     349                $q = new WP_Meta_Query();
     350                $found = $q->sanitize_query( array(
     351                        'relation' => 'FOO',
     352                        array(
     353                                'key' => 'foo',
     354                                'value' => 'bar',
     355                        ),
     356                        array(
     357                                'key' => 'foo2',
     358                                'value' => 'bar2',
     359                        ),
     360                ) );
     361
     362                $this->assertEquals( $expected, $found );
     363        }
     364
     365        public function test_sanitize_query_single_query_which_is_a_nested_query() {
     366                $expected = array(
     367                        'relation' => 'OR',
     368                        array(
     369                                'relation' => 'AND',
     370                                array(
     371                                        'key' => 'foo',
     372                                        'value' => 'bar',
     373                                ),
     374                                array(
     375                                        'key' => 'foo2',
     376                                        'value' => 'bar2',
     377                                ),
     378                        )
     379                );
     380
     381                $q = new WP_Meta_Query();
     382                $found = $q->sanitize_query( array(
     383                        array(
     384                                array(
     385                                        'key' => 'foo',
     386                                        'value' => 'bar',
     387                                ),
     388                                array(
     389                                        'key' => 'foo2',
     390                                        'value' => 'bar2',
     391                                ),
     392                        ),
     393                ) );
     394
     395                $this->assertEquals( $expected, $found );
     396        }
     397
     398        public function test_sanitize_query_multiple_nested_queries() {
     399                $expected = array(
     400                        'relation' => 'OR',
     401                        array(
     402                                'relation' => 'AND',
     403                                array(
     404                                        'key' => 'foo',
     405                                        'value' => 'bar',
     406                                ),
     407                                array(
     408                                        'key' => 'foo2',
     409                                        'value' => 'bar2',
     410                                ),
     411                        ),
     412                        array(
     413                                'relation' => 'AND',
     414                                array(
     415                                        'key' => 'foo3',
     416                                        'value' => 'bar3',
     417                                ),
     418                                array(
     419                                        'key' => 'foo4',
     420                                        'value' => 'bar4',
     421                                ),
     422                        ),
     423                );
     424
     425                $q = new WP_Meta_Query();
     426                $found = $q->sanitize_query( array(
     427                        'relation' => 'OR',
     428                        array(
     429                                array(
     430                                        'key' => 'foo',
     431                                        'value' => 'bar',
     432                                ),
     433                                array(
     434                                        'key' => 'foo2',
     435                                        'value' => 'bar2',
     436                                ),
     437                        ),
     438                        array(
     439                                array(
     440                                        'key' => 'foo3',
     441                                        'value' => 'bar3',
     442                                ),
     443                                array(
     444                                        'key' => 'foo4',
     445                                        'value' => 'bar4',
     446                                ),
     447                        ),
     448                ) );
     449
     450                $this->assertEquals( $expected, $found );
     451        }
     452
    205453        /**
    206454         * Invalid $type will fail to get a table from _get_meta_table()
    207455         */
    class Tests_Meta_Query extends WP_UnitTestCase { 
    229477                $sql = $query->get_sql( 'post', $wpdb->posts, 'ID', $this );
    230478
    231479                // We should have 2 joins - one for my_first_key and one for my_second_key
    232                 $this->assertEquals( 2, substr_count( $sql['join'], 'INNER JOIN' ) );
     480                $this->assertEquals( 2, substr_count( $sql['join'], 'JOIN' ) );
    233481
    234482                // The WHERE should check my_third_key against an unaliased table
    235483                $this->assertEquals( 1, substr_count( $sql['where'], "$wpdb->postmeta.meta_key = 'my_third_key'" ) );
    class Tests_Meta_Query extends WP_UnitTestCase { 
    247495                ) );
    248496                $sql = $query->get_sql( 'post', $wpdb->posts, 'ID', $this );
    249497
    250                 $this->assertEquals( 1, substr_count( $sql['where'], "CAST($wpdb->postmeta.meta_value AS CHAR) = '')" ) );
     498                $this->assertEquals( 1, substr_count( $sql['where'], "CAST($wpdb->postmeta.meta_value AS CHAR) = ''" ) );
    251499        }
    252500
    253501        /**
    class Tests_Meta_Query extends WP_UnitTestCase { 
    558806                ) );
    559807
    560808                $sql = $query->get_sql( 'post', $wpdb->posts, 'ID', $this );
    561                 $this->assertContains( "{$wpdb->postmeta}.meta_key = 'exclude'\nOR", $sql['where'] );
     809
     810                // Use regex because we don't care about the whitespace before OR.
     811                $this->assertRegExp( "/{$wpdb->postmeta}\.meta_key = \'exclude\'\s+OR/", $sql['where'] );
    562812                $this->assertNotContains( "{$wpdb->postmeta}.post_id IS NULL", $sql['where'] );
    563813        }
    564814}
  • tests/phpunit/tests/post/query.php

    diff --git tests/phpunit/tests/post/query.php tests/phpunit/tests/post/query.php
    index 45b1abc..03edcf9 100644
    class Tests_Post_Query extends WP_UnitTestCase { 
    772772                $this->assertEqualSets( array( $post_4, $post_3, $post_2, $post_1 ), $query->posts );
    773773        }
    774774
     775        public function test_meta_query_nested() {
     776                $p1 = $this->factory->post->create();
     777                $p2 = $this->factory->post->create();
     778                $p3 = $this->factory->post->create();
     779
     780                add_post_meta( $p1, 'foo', 'bar' );
     781                add_post_meta( $p2, 'foo2', 'bar' );
     782                add_post_meta( $p3, 'foo2', 'bar' );
     783                add_post_meta( $p3, 'foo3', 'bar' );
     784
     785                $query = new WP_Query( array(
     786                        'update_post_meta_cache' => false,
     787                        'update_term_meta_cache' => false,
     788                        'fields' => 'ids',
     789                        'meta_query' => array(
     790                                'relation' => 'OR',
     791                                array(
     792                                        'key' => 'foo',
     793                                        'value' => 'bar',
     794                                ),
     795                                array(
     796                                        'relation' => 'AND',
     797                                        array(
     798                                                'key' => 'foo2',
     799                                                'value' => 'bar',
     800                                        ),
     801                                        array(
     802                                                'key' => 'foo3',
     803                                                'value' => 'bar',
     804                                        ),
     805                                ),
     806                        ),
     807                ) );
     808
     809                $expected = array( $p1, $p3 );
     810                $this->assertEqualSets( $expected, $query->posts );
     811        }
     812
     813        public function test_meta_query_nested_two_levels_deep() {
     814                $p1 = $this->factory->post->create();
     815                $p2 = $this->factory->post->create();
     816                $p3 = $this->factory->post->create();
     817
     818                add_post_meta( $p1, 'foo', 'bar' );
     819                add_post_meta( $p3, 'foo2', 'bar' );
     820                add_post_meta( $p3, 'foo3', 'bar' );
     821                add_post_meta( $p3, 'foo4', 'bar' );
     822
     823                $query = new WP_Query( array(
     824                        'update_post_meta_cache' => false,
     825                        'update_term_meta_cache' => false,
     826                        'fields' => 'ids',
     827                        'meta_query' => array(
     828                                'relation' => 'OR',
     829                                array(
     830                                        'key' => 'foo',
     831                                        'value' => 'bar',
     832                                ),
     833                                array(
     834                                        'relation' => 'OR',
     835                                        array(
     836                                                'key' => 'foo2',
     837                                                'value' => 'bar',
     838                                        ),
     839                                        array(
     840                                                'relation' => 'AND',
     841                                                array(
     842                                                        'key' => 'foo3',
     843                                                        'value' => 'bar',
     844                                                ),
     845                                                array(
     846                                                        'key' => 'foo4',
     847                                                        'value' => 'bar',
     848                                                ),
     849                                        ),
     850                                ),
     851                        ),
     852                ) );
     853
     854                $expected = array( $p1, $p3 );
     855                $this->assertEqualSets( $expected, $query->posts );
     856        }
     857
    775858        /**
    776859         * @ticket 20604
    777860         */