Ticket #29642: 29642.4.patch
File 29642.4.patch, 30.4 KB (added by , 10 years ago) |
---|
-
src/wp-includes/meta.php
diff --git src/wp-includes/meta.php src/wp-includes/meta.php index e3c43cf..9244e70 100644
function get_meta_sql( $meta_query, $type, $primary_table, $primary_id_column, $ 849 849 } 850 850 851 851 /** 852 * Container class for a multiple metadata query 852 * Class for generating SQL clauses that filter a primary query according to metadata keys and values. 853 * 854 * WP_Meta_Query is a helper that allows primary query classes, such as WP_Query 855 * and WP_User_Query, to filter their results by object metadata, by generating 856 * JOIN and WHERE subclauses to be attached to the primary SQL query string. 857 * 858 * The fundamental unit of a meta query is the "first-order clause", an array 859 * of arguments of the following form: 860 * array( 861 * 'key' => 'your_meta_key', 862 * 'value' => 'Some value', 863 * 'compare' => '=', 864 * 'type' => 'CHAR', 865 * ) 866 * (See the documentation for {@link WP_Meta_Query::__construct()} for more 867 * details on accepted paramaters.) WP_Meta_Query accepts an array of these 868 * first-order clauses, which can be joined by a 'relation' of 'AND' or 'OR. 869 * 870 * Furthermore, these arrays can be nested and combined using multiple values 871 * of 'relation', to generate queries of arbitrary complexity. For example: 872 * array( 873 * 'relation' => 'OR', 874 * array( 875 * 'key' => 'city', 876 * 'value' => 'New York', 877 * ), 878 * array( 879 * 'relation' => 'AND', 880 * array( 881 * 'key' => 'state', 882 * 'value' => 'Massachusetts', 883 * ), 884 * array( 885 * 'key' => 'city' 886 * 'value' => 'Boston', 887 * 'compare' => '!=', 888 * ), 889 * ), 890 * ) 891 * will return items that are either labeled New York, or labeled Massachusetts 892 * but not Boston. 853 893 * 854 894 * @since 3.2.0 855 895 */ 856 896 class WP_Meta_Query { 857 897 /** 858 * List of metadata queries. A single query is an associative array: 859 * - 'key' string The meta key 860 * - 'value' string|array The meta value 861 * - 'compare' (optional) string How to compare the key to the value. 862 * Possible values: '=', '!=', '>', '>=', '<', '<=', 'LIKE', 'NOT LIKE', 'IN', 'NOT IN', 863 * 'BETWEEN', 'NOT BETWEEN', 'REGEXP', 'NOT REGEXP', 'RLIKE'. 864 * Default: '=' 865 * - 'type' string (optional) The type of the value. 866 * Possible values: 'NUMERIC', 'BINARY', 'CHAR', 'DATE', 'DATETIME', 'DECIMAL', 'SIGNED', 'TIME', 'UNSIGNED'. 867 * Default: 'CHAR' 898 * Array of metadata queries. 868 899 * 869 900 * @since 3.2.0 870 901 * @access public 871 902 * @var array 903 * @see WP_Meta_Query::__construct() for description of params. 872 904 */ 873 905 public $queries = array(); 874 906 … … class WP_Meta_Query { 882 914 public $relation; 883 915 884 916 /** 885 * Constructor 917 * Database table to query for the metadata. 918 * 919 * @access public 920 * @var string 921 */ 922 public $meta_table; 923 924 /** 925 * Column in meta_table that represents the ID of the object the metadata belongs to. 886 926 * 887 * @param array $meta_query (optional) A meta query 927 * @access public 928 * @var string 929 */ 930 public $meta_id_column; 931 932 /** 933 * Database table that where the metadata's objects are stored (eg $wpdb->users). 934 * 935 * @access public 936 * @var string 937 */ 938 public $primary_table; 939 940 /** 941 * Column in primary_table that represents the ID of the object. 942 * 943 * @access public 944 * @var string 945 */ 946 public $primary_id_column; 947 948 /** 949 * A flat list of table aliases used in JOIN clauses. 950 * 951 * @since 4.1.0 952 * @access protected 953 * @var array 954 */ 955 protected $table_aliases = array(); 956 957 /** 958 * Constructor. 959 * 960 * @param array $meta_query { 961 * Meta query clauses. 962 * @type string $relation Optional. The MySQL keyword used to join 963 * the clauses of the query. (AND|OR) 964 * @type array { 965 * A first-order clause, or another fully-formed meta query. 966 * Parameters for first-order clauses are described here. All are optional. 967 * @type string $key Meta key to filter by. 968 * @type string $value Meta value to filter by. 969 * @type string $compare MySQL operator used for comparing the $value. 970 * Default: 'IN when $value is an array, otherwise '='. 971 * (=|!=|>|>=|<|<=|LIKE|NOT LIKE|IN|NOT IN|BETWEEN|NOT BETWEEN|REGEXP|NOT REGEXP|RLIKE) 972 * @type string $type MySQL data type that the meta_value column will be CAST to 973 * for comparisons. Default: 'CHAR'. 974 * (NUMERIC|BINARY|CHAR|DATE|DATETIME|DECIMAL|SIGNED|TIME|UNSIGNED) 975 * } 976 * } 888 977 */ 889 978 public function __construct( $meta_query = false ) { 890 979 if ( !$meta_query ) … … class WP_Meta_Query { 896 985 $this->relation = 'AND'; 897 986 } 898 987 899 $this->queries = array(); 988 $this->queries = $this->sanitize_query( $meta_query ); 989 } 990 991 /** 992 * Ensure that the meta_query argument passed to the class constructor is well-formed. 993 * 994 * Eliminates empty items and ensures that a 'relation' is set. 995 * 996 * @param array $queries Array of query clauses. 997 * @return array Sanitized array of query clauses. 998 */ 999 public function sanitize_query( $queries ) { 1000 $clean_queries = array(); 1001 1002 if ( ! is_array( $queries ) ) { 1003 return $clean_queries; 1004 } 1005 1006 foreach ( $queries as $key => $query ) { 1007 if ( 'relation' === $key ) { 1008 $relation = $query; 1009 1010 // First-order clause. 1011 } else if ( $this->is_first_order_clause( $query ) ) { 1012 $clean_queries[] = $query; 1013 1014 // Otherwise, it's a nested query. 1015 } else { 1016 $cleaned_query = $this->sanitize_query( $query ); 1017 1018 if ( ! empty( $cleaned_query ) ) { 1019 $clean_queries[] = $cleaned_query; 1020 } 1021 } 1022 } 1023 1024 if ( empty( $clean_queries ) ) { 1025 return $clean_queries; 1026 } 900 1027 901 foreach ( $meta_query as $key => $query ) {902 if ( ! is_array( $query ) )903 continue;1028 // Sanitize the 'relation' key provided in the query. 1029 if ( isset( $relation ) && 'OR' === strtoupper( $relation ) ) { 1030 $clean_queries['relation'] = 'OR'; 904 1031 905 $this->queries[] = $query; 1032 /* 1033 * If there is only a single clause, call the relation 'OR'. 1034 * This value will not actually be used to join clauses, but it 1035 * simplifies the logic around combining key-only queries. 1036 */ 1037 } else if ( 1 === count( $clean_queries ) ) { 1038 $clean_queries['relation'] = 'OR'; 1039 1040 // Default to AND. 1041 } else { 1042 $clean_queries['relation'] = 'AND'; 906 1043 } 1044 1045 return $clean_queries; 1046 } 1047 1048 /** 1049 * Determine whether a query clause is first-order. 1050 * 1051 * A first-order meta query clause is one that has either a 'key' or 1052 * a 'value' array key. 1053 * 1054 * @since 4.1.0 1055 * @access protected 1056 * 1057 * @param array $query Meta query arguments. 1058 * @return bool 1059 */ 1060 protected function is_first_order_clause( $query ) { 1061 return isset( $query['key'] ) || isset( $query['value'] ); 907 1062 } 908 1063 909 1064 /** … … class WP_Meta_Query { 963 1118 * @since 3.2.0 964 1119 * @access public 965 1120 * 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 ) 1121 * @param string $type Type of meta, eg 'user', 'post'. 1122 * @param string $primary_table Database table where the object being 1123 * filtered is stored (eg wp_users). 1124 * @param string $primary_id_column ID column for the filtered object in $primary_table. 1125 * @param object $context Optional. The main query object. 1126 * @return array { 1127 * @type string $join SQL fragment to append to the main JOIN clause. 1128 * @type string $where SQL fragment to append to the main WHERE clause. 1129 * } 971 1130 */ 972 1131 public function get_sql( $type, $primary_table, $primary_id_column, $context = null ) { 973 1132 global $wpdb; 974 1133 975 if ( ! $meta_table = _get_meta_table( $type ) ) 1134 if ( ! $meta_table = _get_meta_table( $type ) ) { 976 1135 return false; 1136 } 977 1137 978 $meta_id_column = sanitize_key( $type . '_id' ); 1138 $this->meta_table = $meta_table; 1139 $this->meta_id_column = sanitize_key( $type . '_id' ); 979 1140 980 $ join = array();981 $ where = array();1141 $this->primary_table = $primary_table; 1142 $this->primary_id_column = $primary_id_column; 982 1143 983 $key_only_queries = array(); 984 $queries = array(); 1144 $sql = $this->get_sql_clauses(); 985 1145 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 } 1146 /** 1147 * Filter the meta query's generated SQL. 1148 * 1149 * @since 3.1.0 1150 * 1151 * @param array $args { 1152 * An array of arguments. 1153 * 1154 * @type array $clauses Array containing the query's JOIN and WHERE clauses. 1155 * @type array $queries Array of meta queries. 1156 * @type string $type Type of meta. 1157 * @type string $primary_table Primary table. 1158 * @type string $primary_id_column Primary column ID. 1159 * @type object $context The main query object. 1160 * } 1161 */ 1162 return apply_filters_ref_array( 'get_meta_sql', array( $sql, $this->queries, $type, $primary_table, $primary_id_column, $context ) ); 1163 } 1164 1165 /** 1166 * Generate SQL clauses to be appended to a main query. 1167 * 1168 * Called by the public {@link WP_Meta_Query::get_sql()}, this method 1169 * is abstracted out to maintain parity with the other Query classes. 1170 * 1171 * @since 4.1.0 1172 * @access protected 1173 * 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 protected function get_sql_clauses() { 1180 $sql = $this->get_sql_for_query( $this->queries ); 1181 1182 if ( ! empty( $sql['where'] ) ) { 1183 $sql['where'] = ' AND ' . "\n" . $sql['where'] . "\n"; 992 1184 } 993 1185 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; 1186 return $sql; 1187 } 1188 1189 /** 1190 * Generate SQL clauses for a single query array. 1191 * 1192 * If nested subqueries are found, this method recurses the tree to 1193 * produce the properly nested SQL. 1194 * 1195 * @since 4.1.0 1196 * @access protected 1197 * 1198 * @param array $query Query to parse. 1199 * @param int $depth Optional. Number of tree levels deep we 1200 * currently are. Used to calculate indentation. 1201 * @return array { 1202 * @type string $join SQL fragment to append to the main JOIN clause. 1203 * @type string $where SQL fragment to append to the main WHERE clause. 1204 * } 1205 */ 1206 protected function get_sql_for_query( $query, $depth = 0 ) { 1207 $sql_chunks = array( 1208 'join' => array(), 1209 'where' => array(), 1210 ); 1211 1212 $sql = array( 1213 'join' => '', 1214 'where' => '', 1215 ); 1216 1217 $indent = ''; 1218 for ( $i = 0; $i < $depth; $i++ ) { 1219 $indent .= "\t"; 1220 } 1221 1222 foreach ( $query as $key => $clause ) { 1223 if ( 'relation' === $key ) { 1224 $relation = $query['relation']; 1225 } else if ( is_array( $clause ) ) { 1226 1227 // This is a first-order clause. 1228 if ( $this->is_first_order_clause( $clause ) ) { 1229 $clause_sql = $this->get_sql_for_clause( $clause, $query ); 1230 1231 $where_count = count( $clause_sql['where'] ); 1232 if ( ! $where_count ) { 1233 $sql_chunks['where'][] = ''; 1234 } else if ( 1 === $where_count ) { 1235 $sql_chunks['where'][] = $clause_sql['where'][0]; 1236 } else { 1237 $sql_chunks['where'][] = '( ' . implode( ' AND ', $clause_sql['where'] ) . ' )'; 1238 } 1239 1240 $sql_chunks['join'] = array_merge( $sql_chunks['join'], $clause_sql['join'] ); 1241 // This is a subquery, so we recurse. 1242 } else { 1243 $clause_sql = $this->get_sql_for_query( $clause, $depth + 1 ); 1244 1245 $sql_chunks['where'][] = $clause_sql['where']; 1246 $sql_chunks['join'][] = $clause_sql['join']; 1247 } 1001 1248 } 1002 } else {1003 $queries = $this->queries;1004 1249 } 1005 1250 1006 // Specify all the meta_key only queries in one go1007 if ( $key_only_queries ) {1008 $join[] = "INNER JOIN $meta_table ON $primary_table.$primary_id_column = $meta_table.$meta_id_column";1251 // Filter to remove empties. 1252 $sql_chunks['join'] = array_filter( $sql_chunks['join'] ); 1253 $sql_chunks['where'] = array_filter( $sql_chunks['where'] ); 1009 1254 1010 foreach ( $key_only_queries as $key => $q )1011 $where["key-only-$key"] = $wpdb->prepare( "$meta_table.meta_key = %s", trim( $q['key'] ) );1255 if ( empty( $relation ) ) { 1256 $relation = 'AND'; 1012 1257 } 1013 1258 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'] : '' ); 1259 // Filter duplicate JOIN clauses and combine into a single string. 1260 if ( ! empty( $sql_chunks['join'] ) ) { 1261 $sql['join'] = implode( ' ', array_unique( $sql_chunks['join'] ) ); 1262 } 1017 1263 1018 if ( array_key_exists( 'value', $q ) && is_null( $q['value'] ) ) 1019 $q['value'] = ''; 1264 // Generate a single WHERE clause with proper brackets and indentation. 1265 if ( ! empty( $sql_chunks['where'] ) ) { 1266 $sql['where'] = '( ' . "\n\t" . $indent . implode( ' ' . "\n\t" . $indent . $relation . ' ' . "\n\t" . $indent, $sql_chunks['where'] ) . "\n" . $indent . ')' . "\n"; 1267 } 1020 1268 1021 $meta_value = isset( $q['value'] ) ? $q['value'] : null; 1269 return $sql; 1270 } 1022 1271 1023 if ( isset( $q['compare'] ) ) 1024 $meta_compare = strtoupper( $q['compare'] ); 1025 else 1026 $meta_compare = is_array( $meta_value ) ? 'IN' : '='; 1272 /** 1273 * Generate SQL JOIN and WHERE clauses for a first-order query clause. 1274 * 1275 * "First-order" means that it's an array with a 'key' or 'value'. 1276 * 1277 * @param array $clause Query clause. 1278 * @param array $parent_query Parent query array. 1279 * @return array { 1280 * @type string $join SQL fragment to append to the main JOIN clause. 1281 * @type string $where SQL fragment to append to the main WHERE clause. 1282 * } 1283 */ 1284 public function get_sql_for_clause( $clause, $parent_query ) { 1285 global $wpdb; 1027 1286 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 = '='; 1287 $sql_chunks = array( 1288 'where' => array(), 1289 'join' => array(), 1290 ); 1037 1291 1038 $i = count( $join);1039 $alias = $i ? 'mt' . $i : $meta_table;1292 $i = count( $this->table_aliases ); 1293 $alias = $i ? 'mt' . $i : $this->meta_table; 1040 1294 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')"; 1295 if ( isset( $clause['compare'] ) ) { 1296 $meta_compare = strtoupper( $clause['compare'] ); 1297 } else { 1298 $meta_compare = isset( $clause['value'] ) && is_array( $clause['value'] ) ? 'IN' : '='; 1299 } 1045 1300 1046 $where[$k] = ' ' . $alias . '.' . $meta_id_column . ' IS NULL'; 1301 if ( ! in_array( $meta_compare, array( 1302 '=', '!=', '>', '>=', '<', '<=', 1303 'LIKE', 'NOT LIKE', 1304 'IN', 'NOT IN', 1305 'BETWEEN', 'NOT BETWEEN', 1306 'EXISTS', 'NOT EXISTS', 1307 'REGEXP', 'NOT REGEXP', 'RLIKE' 1308 ) ) ) { 1309 $meta_compare = '='; 1310 } 1047 1311 1048 continue; 1312 /* 1313 * There are a number of different query structures that get 1314 * built in different ways. 1315 * 1. Key-only clauses - (a) clauses without a 'value' key that 1316 * appear in the context of an OR relation and do not use 1317 * 'NOT EXISTS' as the 'compare', or (b) clauses with an 1318 * empty array for 'value'. 1319 */ 1320 if ( ! empty( $clause['key'] ) && ( 1321 ( ! array_key_exists( 'value', $clause ) && 'NOT EXISTS' !== $meta_compare && 'OR' === $parent_query['relation'] ) || 1322 ( isset( $clause['value'] ) && is_array( $clause['value'] ) && empty( $clause['value'] ) ) 1323 ) ) { 1324 1325 $alias = $this->meta_table; 1326 $sql_chunks['join'][] = " INNER JOIN $this->meta_table ON ($this->primary_table.$this->primary_id_column = $alias.$this->meta_id_column)"; 1327 $sql_chunks['where'][] = $wpdb->prepare( "$this->meta_table.meta_key = %s", trim( $clause['key'] ) ); 1328 1329 // 2. NOT EXISTS. 1330 } else if ( 'NOT EXISTS' === $meta_compare ) { 1331 $join = " LEFT JOIN $this->meta_table"; 1332 $join .= $i ? " AS $alias" : ''; 1333 $join .= $wpdb->prepare( " ON ($this->primary_table.$this->primary_id_column = $alias.$this->meta_id_column AND $alias.meta_key = %s )", $clause['key'] ); 1334 $sql_chunks['join'][] = $join; 1335 1336 $sql_chunks['where'][] = $alias . '.' . $this->meta_id_column . ' IS NULL'; 1337 1338 // 3. EXISTS and other key-only queries. 1339 } else if ( 'EXISTS' === $meta_compare || ( ! empty( $clause['key'] ) && ! array_key_exists( 'value', $clause ) ) ) { 1340 $join = " INNER JOIN $this->meta_table"; 1341 $join .= $i ? " AS $alias" : ''; 1342 $join .= " ON ( $this->primary_table.$this->primary_id_column = $alias.$this->meta_id_column )"; 1343 $sql_chunks['join'][] = $join; 1344 1345 $sql_chunks['where'][] = $wpdb->prepare( $alias . '.meta_key = %s', trim( $clause['key'] ) ); 1346 1347 // 4. Clauses that have a value. 1348 } else if ( array_key_exists( 'value', $clause ) ) { 1349 $join = " INNER JOIN $this->meta_table"; 1350 $join .= $i ? " AS $alias" : ''; 1351 $join .= " ON ($this->primary_table.$this->primary_id_column = $alias.$this->meta_id_column)"; 1352 $sql_chunks['join'][] = $join; 1353 1354 if ( ! empty( $clause['key'] ) ) { 1355 $sql_chunks['where'][] = $wpdb->prepare( "$alias.meta_key = %s", trim( $clause['key'] ) ); 1049 1356 } 1050 1357 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 ); 1358 $meta_type = $this->get_cast_for_type( isset( $clause['type'] ) ? $clause['type'] : '' ); 1058 1359 1059 if ( is_null( $meta_value ) ) { 1060 if ( empty( $where[$k] ) ) 1061 unset( $join[$i] ); 1062 continue; 1063 } 1360 $meta_value = isset( $clause['value'] ) ? $clause['value'] : ''; 1064 1361 1065 1362 if ( in_array( $meta_compare, array( 'IN', 'NOT IN', 'BETWEEN', 'NOT BETWEEN' ) ) ) { 1066 if ( ! is_array( $meta_value ) ) 1363 if ( ! is_array( $meta_value ) ) { 1067 1364 $meta_value = preg_split( '/[,\s]+/', $meta_value ); 1068 1069 if ( empty( $meta_value ) ) {1070 unset( $join[$i] );1071 continue;1072 1365 } 1073 1366 } else { 1074 1367 $meta_value = trim( $meta_value ); 1075 1368 } 1076 1369 1077 if ( 'IN' == substr( $meta_compare, -2 ) ) {1370 if ( 'IN' == substr( $meta_compare, -2 ) ) { 1078 1371 $meta_compare_string = '(' . substr( str_repeat( ',%s', count( $meta_value ) ), 1 ) . ')'; 1079 } elseif ( 'BETWEEN' == substr( $meta_compare, -7 ) ) {1372 } elseif ( 'BETWEEN' == substr( $meta_compare, -7 ) ) { 1080 1373 $meta_value = array_slice( $meta_value, 0, 2 ); 1081 1374 $meta_compare_string = '%s AND %s'; 1082 1375 } elseif ( 'LIKE' == $meta_compare || 'NOT LIKE' == $meta_compare ) { … … class WP_Meta_Query { 1086 1379 $meta_compare_string = '%s'; 1087 1380 } 1088 1381 1089 if ( ! empty( $where[$k] ) ) 1090 $where[$k] .= ' AND '; 1091 1092 $where[$k] = ' (' . $where[$k] . $wpdb->prepare( "CAST($alias.meta_value AS {$meta_type}) {$meta_compare} {$meta_compare_string})", $meta_value ); 1382 $sql_chunks['where'][] = $wpdb->prepare( "CAST($alias.meta_value AS {$meta_type}) {$meta_compare} {$meta_compare_string}", $meta_value ); 1093 1383 } 1094 1384 1095 $where = array_filter( $where ); 1096 1097 if ( empty( $where ) ) 1098 $where = ''; 1099 else 1100 $where = ' AND (' . implode( "\n{$this->relation} ", $where ) . ' )'; 1385 /* 1386 * Multiple WHERE clauses (for meta_key and meta_value) should 1387 * be joined in parentheses. 1388 */ 1389 if ( 1 < count( $sql_chunks['where'] ) ) { 1390 $sql_chunks['where'] = array( '( ' . implode( ' AND ', $sql_chunks['where'] ) . ' )' ); 1391 } 1101 1392 1102 $join = implode( "\n", $join ); 1103 if ( ! empty( $join ) ) 1104 $join = ' ' . $join; 1393 $this->table_aliases[] = $alias; 1105 1394 1106 /** 1107 * Filter the meta query's generated SQL. 1108 * 1109 * @since 3.1.0 1110 * 1111 * @param array $args { 1112 * An array of arguments. 1113 * 1114 * @type array $clauses Array containing the query's JOIN and WHERE clauses. 1115 * @type array $queries Array of meta queries. 1116 * @type string $type Type of meta. 1117 * @type string $primary_table Primary table. 1118 * @type string $primary_id_column Primary column ID. 1119 * @type object $context The main query object. 1120 * } 1121 */ 1122 return apply_filters_ref_array( 'get_meta_sql', array( compact( 'join', 'where' ), $this->queries, $type, $primary_table, $primary_id_column, $context ) ); 1395 return $sql_chunks; 1123 1396 } 1124 1397 } 1125 1398 -
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 { 40 40 array(), 41 41 ) ); 42 42 43 $this->assertSame( array( array()), $query->queries );43 $this->assertSame( array(), $query->queries ); 44 44 } 45 45 46 46 /** … … class Tests_Meta_Query extends WP_UnitTestCase { 153 153 $query = new WP_Meta_Query(); 154 154 155 155 // 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 ); 159 166 160 167 // 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 ); 164 180 165 181 // 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 ); 169 194 } 170 195 171 196 /** … … class Tests_Meta_Query extends WP_UnitTestCase { 202 227 $this->assertEquals( 'CHAR', $query->get_cast_for_type( 'ANYTHING ELSE' ) ); 203 228 } 204 229 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 205 453 /** 206 454 * Invalid $type will fail to get a table from _get_meta_table() 207 455 */ … … class Tests_Meta_Query extends WP_UnitTestCase { 229 477 $sql = $query->get_sql( 'post', $wpdb->posts, 'ID', $this ); 230 478 231 479 // We should have 2 joins - one for my_first_key and one for my_second_key 232 $this->assertEquals( 2, substr_count( $sql['join'], ' INNERJOIN' ) );480 $this->assertEquals( 2, substr_count( $sql['join'], 'JOIN' ) ); 233 481 234 482 // The WHERE should check my_third_key against an unaliased table 235 483 $this->assertEquals( 1, substr_count( $sql['where'], "$wpdb->postmeta.meta_key = 'my_third_key'" ) ); … … class Tests_Meta_Query extends WP_UnitTestCase { 247 495 ) ); 248 496 $sql = $query->get_sql( 'post', $wpdb->posts, 'ID', $this ); 249 497 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) = ''" ) ); 251 499 } 252 500 253 501 /** … … class Tests_Meta_Query extends WP_UnitTestCase { 558 806 ) ); 559 807 560 808 $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'] ); 562 812 $this->assertNotContains( "{$wpdb->postmeta}.post_id IS NULL", $sql['where'] ); 563 813 } 564 814 } -
tests/phpunit/tests/post/query.php
diff --git tests/phpunit/tests/post/query.php tests/phpunit/tests/post/query.php index 0539edc..dbe157e 100644
class Tests_Post_Query extends WP_UnitTestCase { 1408 1408 $this->assertEquals( array( $p1, $p2 ), $q->posts ); 1409 1409 } 1410 1410 1411 public function test_meta_query_nested() { 1412 $p1 = $this->factory->post->create(); 1413 $p2 = $this->factory->post->create(); 1414 $p3 = $this->factory->post->create(); 1415 1416 add_post_meta( $p1, 'foo', 'bar' ); 1417 add_post_meta( $p2, 'foo2', 'bar' ); 1418 add_post_meta( $p3, 'foo2', 'bar' ); 1419 add_post_meta( $p3, 'foo3', 'bar' ); 1420 1421 $query = new WP_Query( array( 1422 'update_post_meta_cache' => false, 1423 'update_term_meta_cache' => false, 1424 'fields' => 'ids', 1425 'meta_query' => array( 1426 'relation' => 'OR', 1427 array( 1428 'key' => 'foo', 1429 'value' => 'bar', 1430 ), 1431 array( 1432 'relation' => 'AND', 1433 array( 1434 'key' => 'foo2', 1435 'value' => 'bar', 1436 ), 1437 array( 1438 'key' => 'foo3', 1439 'value' => 'bar', 1440 ), 1441 ), 1442 ), 1443 ) ); 1444 1445 $expected = array( $p1, $p3 ); 1446 $this->assertEqualSets( $expected, $query->posts ); 1447 } 1448 1449 public function test_meta_query_nested_two_levels_deep() { 1450 $p1 = $this->factory->post->create(); 1451 $p2 = $this->factory->post->create(); 1452 $p3 = $this->factory->post->create(); 1453 1454 add_post_meta( $p1, 'foo', 'bar' ); 1455 add_post_meta( $p3, 'foo2', 'bar' ); 1456 add_post_meta( $p3, 'foo3', 'bar' ); 1457 add_post_meta( $p3, 'foo4', 'bar' ); 1458 1459 $query = new WP_Query( array( 1460 'update_post_meta_cache' => false, 1461 'update_term_meta_cache' => false, 1462 'fields' => 'ids', 1463 'meta_query' => array( 1464 'relation' => 'OR', 1465 array( 1466 'key' => 'foo', 1467 'value' => 'bar', 1468 ), 1469 array( 1470 'relation' => 'OR', 1471 array( 1472 'key' => 'foo2', 1473 'value' => 'bar', 1474 ), 1475 array( 1476 'relation' => 'AND', 1477 array( 1478 'key' => 'foo3', 1479 'value' => 'bar', 1480 ), 1481 array( 1482 'key' => 'foo4', 1483 'value' => 'bar', 1484 ), 1485 ), 1486 ), 1487 ), 1488 ) ); 1489 1490 $expected = array( $p1, $p3 ); 1491 $this->assertEqualSets( $expected, $query->posts ); 1492 } 1493 1411 1494 /** 1412 1495 * @ticket 20604 1413 1496 * @group taxonomy