Changeset 29887
- Timestamp:
- 10/13/2014 10:02:18 PM (10 years ago)
- Location:
- trunk
- Files:
-
- 3 edited
Legend:
- Unmodified
- Added
- Removed
-
trunk/src/wp-includes/meta.php
r29650 r29887 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 {@see WP_Query} and {@see WP_User_Query}, 855 * to filter their results by object metadata, by generating `JOIN` and `WHERE` subclauses to be attached 856 * to the primary SQL query string. 853 857 * 854 858 * @since 3.2.0 … … 856 860 class WP_Meta_Query { 857 861 /** 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' 868 * 869 * @since 3.2.0 870 * @access public 871 * @var array 872 */ 862 * Array of metadata queries. 863 * 864 * See {@see WP_Meta_Query::__construct()} for information on meta query arguments. 865 * 866 * @since 3.2.0 867 * @access public 868 * @var array 869 */ 873 870 public $queries = array(); 874 871 … … 883 880 884 881 /** 885 * Constructor 886 * 887 * @param array $meta_query (optional) A meta query 882 * Database table to query for the metadata. 883 * 884 * @since 4.1.0 885 * @access public 886 * @var string 887 */ 888 public $meta_table; 889 890 /** 891 * Column in meta_table that represents the ID of the object the metadata belongs to. 892 * 893 * @since 4.1.0 894 * @access public 895 * @var string 896 */ 897 public $meta_id_column; 898 899 /** 900 * Database table that where the metadata's objects are stored (eg $wpdb->users). 901 * 902 * @since 4.1.0 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 * @since 4.1.0 912 * @access public 913 * @var string 914 */ 915 public $primary_id_column; 916 917 /** 918 * A flat list of table aliases used in JOIN clauses. 919 * 920 * @since 4.1.0 921 * @access protected 922 * @var array 923 */ 924 protected $table_aliases = array(); 925 926 /** 927 * Constructor. 928 * 929 * @since 3.2.0 930 * @access public 931 * 932 * @param array $meta_query { 933 * Array of meta query clauses. 934 * 935 * @type string $relation Optional. The MySQL keyword used to join 936 * the clauses of the query. Accepts 'AND', or 'OR'. Default 'AND'. 937 * @type array { 938 * Optional. An array of first-order clause parameters, or another fully-formed meta query. 939 * 940 * @type string $key Meta key to filter by. 941 * @type string $value Meta value to filter by. 942 * @type string $compare MySQL operator used for comparing the $value. Accepts '=', 943 * '!=', '>', '>=', '<', '<=', 'LIKE', 'NOT LIKE', 'IN', 'NOT IN', 944 * 'BETWEEN', 'NOT BETWEEN', 'REGEXP', 'NOT REGEXP', or 'RLIKE'. 945 * Default is 'IN' when `$value` is an array, '=' otherwise. 946 * @type string $type MySQL data type that the meta_value column will be CAST to for 947 * comparisons. Accepts 'NUMERIC', 'BINARY', 'CHAR', 'DATE', 948 * 'DATETIME', 'DECIMAL', 'SIGNED', 'TIME', or 'UNSIGNED'. 949 * Default is 'CHAR'. 950 * } 951 * } 888 952 */ 889 953 public function __construct( $meta_query = false ) { … … 897 961 } 898 962 899 $this->queries = array(); 900 901 foreach ( $meta_query as $key => $query ) { 902 if ( ! is_array( $query ) ) 903 continue; 904 905 $this->queries[] = $query; 906 } 963 $this->queries = $this->sanitize_query( $meta_query ); 964 } 965 966 /** 967 * Ensure the `meta_query` argument passed to the class constructor is well-formed. 968 * 969 * Eliminates empty items and ensures that a 'relation' is set. 970 * 971 * @since 4.1.0 972 * @access public 973 * 974 * @param array $queries Array of query clauses. 975 * @return array Sanitized array of query clauses. 976 */ 977 public function sanitize_query( $queries ) { 978 $clean_queries = array(); 979 980 if ( ! is_array( $queries ) ) { 981 return $clean_queries; 982 } 983 984 foreach ( $queries as $key => $query ) { 985 if ( 'relation' === $key ) { 986 $relation = $query; 987 988 // First-order clause. 989 } else if ( $this->is_first_order_clause( $query ) ) { 990 $clean_queries[] = $query; 991 992 // Otherwise, it's a nested query, so we recurse. 993 } else { 994 $cleaned_query = $this->sanitize_query( $query ); 995 996 if ( ! empty( $cleaned_query ) ) { 997 $clean_queries[] = $cleaned_query; 998 } 999 } 1000 } 1001 1002 if ( empty( $clean_queries ) ) { 1003 return $clean_queries; 1004 } 1005 1006 // Sanitize the 'relation' key provided in the query. 1007 if ( isset( $relation ) && 'OR' === strtoupper( $relation ) ) { 1008 $clean_queries['relation'] = 'OR'; 1009 1010 /* 1011 * If there is only a single clause, call the relation 'OR'. 1012 * This value will not actually be used to join clauses, but it 1013 * simplifies the logic around combining key-only queries. 1014 */ 1015 } else if ( 1 === count( $clean_queries ) ) { 1016 $clean_queries['relation'] = 'OR'; 1017 1018 // Default to AND. 1019 } else { 1020 $clean_queries['relation'] = 'AND'; 1021 } 1022 1023 return $clean_queries; 1024 } 1025 1026 /** 1027 * Determine whether a query clause is first-order. 1028 * 1029 * A first-order meta query clause is one that has either a 'key' or 1030 * a 'value' array key. 1031 * 1032 * @since 4.1.0 1033 * @access protected 1034 * 1035 * @param array $query Meta query arguments. 1036 * @return bool Whether the query clause is a first-order clause. 1037 */ 1038 protected function is_first_order_clause( $query ) { 1039 return isset( $query['key'] ) || isset( $query['value'] ); 907 1040 } 908 1041 … … 918 1051 $meta_query = array(); 919 1052 920 // Simple query needs to be first for orderby=meta_value to work correctly 1053 // Simple query needs to be first for orderby=meta_value to work correctly. 921 1054 foreach ( array( 'key', 'compare', 'type' ) as $key ) { 922 1055 if ( !empty( $qv[ "meta_$key" ] ) ) … … 924 1057 } 925 1058 926 // WP_Query sets 'meta_value' = '' by default 1059 // WP_Query sets 'meta_value' = '' by default. 927 1060 if ( isset( $qv[ 'meta_value' ] ) && '' !== $qv[ 'meta_value' ] && ( ! is_array( $qv[ 'meta_value' ] ) || $qv[ 'meta_value' ] ) ) 928 1061 $meta_query[0]['value'] = $qv[ 'meta_value' ]; … … 936 1069 937 1070 /** 938 * Given a meta type, return the appropriate alias if applicable1071 * Return the appropriate alias for the given meta type if applicable. 939 1072 * 940 1073 * @since 3.7.0 941 * 942 * @param string $type MySQL type to cast meta_value 943 * @return string MySQL type 1074 * @access public 1075 * 1076 * @param string $type MySQL type to cast meta_value. 1077 * @return string MySQL type. 944 1078 */ 945 1079 public function get_cast_for_type( $type = '' ) { … … 964 1098 * @access public 965 1099 * 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 ) 1100 * @param string $type Type of meta, eg 'user', 'post'. 1101 * @param string $primary_table Database table where the object being filtered is stored (eg wp_users). 1102 * @param string $primary_id_column ID column for the filtered object in $primary_table. 1103 * @param object $context Optional. The main query object. 1104 * @return array { 1105 * Array containing JOIN and WHERE SQL clauses to append to the main query. 1106 * 1107 * @type string $join SQL fragment to append to the main JOIN clause. 1108 * @type string $where SQL fragment to append to the main WHERE clause. 1109 * } 971 1110 */ 972 1111 public function get_sql( $type, $primary_table, $primary_id_column, $context = null ) { 973 1112 global $wpdb; 974 1113 975 if ( ! $meta_table = _get_meta_table( $type ) ) 1114 if ( ! $meta_table = _get_meta_table( $type ) ) { 976 1115 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(); 985 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 } 1002 } else { 1003 $queries = $this->queries; 1004 } 1005 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'] ) ); 1012 } 1013 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; 1049 } 1050 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 ); 1058 1059 if ( is_null( $meta_value ) ) { 1060 if ( empty( $where[$k] ) ) 1061 unset( $join[$i] ); 1062 continue; 1063 } 1064 1065 if ( in_array( $meta_compare, array( 'IN', 'NOT IN', 'BETWEEN', 'NOT BETWEEN' ) ) ) { 1066 if ( ! is_array( $meta_value ) ) 1067 $meta_value = preg_split( '/[,\s]+/', $meta_value ); 1068 1069 if ( empty( $meta_value ) ) { 1070 unset( $join[$i] ); 1071 continue; 1072 } 1073 } else { 1074 $meta_value = trim( $meta_value ); 1075 } 1076 1077 if ( 'IN' == substr( $meta_compare, -2) ) { 1078 $meta_compare_string = '(' . substr( str_repeat( ',%s', count( $meta_value ) ), 1 ) . ')'; 1079 } elseif ( 'BETWEEN' == substr( $meta_compare, -7) ) { 1080 $meta_value = array_slice( $meta_value, 0, 2 ); 1081 $meta_compare_string = '%s AND %s'; 1082 } elseif ( 'LIKE' == $meta_compare || 'NOT LIKE' == $meta_compare ) { 1083 $meta_value = '%' . $wpdb->esc_like( $meta_value ) . '%'; 1084 $meta_compare_string = '%s'; 1085 } else { 1086 $meta_compare_string = '%s'; 1087 } 1088 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 ); 1093 } 1094 1095 $where = array_filter( $where ); 1096 1097 if ( empty( $where ) ) 1098 $where = ''; 1099 else 1100 $where = ' AND (' . implode( "\n{$this->relation} ", $where ) . ' )'; 1101 1102 $join = implode( "\n", $join ); 1103 if ( ! empty( $join ) ) 1104 $join = ' ' . $join; 1116 } 1117 1118 $this->meta_table = $meta_table; 1119 $this->meta_id_column = sanitize_key( $type . '_id' ); 1120 1121 $this->primary_table = $primary_table; 1122 $this->primary_id_column = $primary_id_column; 1123 1124 $sql = $this->get_sql_clauses(); 1105 1125 1106 1126 /** … … 1110 1130 * 1111 1131 * @param array $args { 1112 * An array of arguments.1132 * An array of meta query SQL arguments. 1113 1133 * 1114 1134 * @type array $clauses Array containing the query's JOIN and WHERE clauses. … … 1120 1140 * } 1121 1141 */ 1122 return apply_filters_ref_array( 'get_meta_sql', array( compact( 'join', 'where' ), $this->queries, $type, $primary_table, $primary_id_column, $context ) ); 1142 return apply_filters_ref_array( 'get_meta_sql', array( $sql, $this->queries, $type, $primary_table, $primary_id_column, $context ) ); 1143 } 1144 1145 /** 1146 * Generate SQL clauses to be appended to a main query. 1147 * 1148 * Called by the public {@see WP_Meta_Query::get_sql()}, this method 1149 * is abstracted out to maintain parity with the other Query classes. 1150 * 1151 * @since 4.1.0 1152 * @access protected 1153 * 1154 * @return array { 1155 * Array containing JOIN and WHERE SQL clauses to append to the main query. 1156 * 1157 * @type string $join SQL fragment to append to the main JOIN clause. 1158 * @type string $where SQL fragment to append to the main WHERE clause. 1159 * } 1160 */ 1161 protected function get_sql_clauses() { 1162 $sql = $this->get_sql_for_query( $this->queries ); 1163 1164 if ( ! empty( $sql['where'] ) ) { 1165 $sql['where'] = ' AND ' . $sql['where']; 1166 } 1167 1168 return $sql; 1169 } 1170 1171 /** 1172 * Generate SQL clauses for a single query array. 1173 * 1174 * If nested subqueries are found, this method recurses the tree to 1175 * produce the properly nested SQL. 1176 * 1177 * @since 4.1.0 1178 * @access protected 1179 * 1180 * @param array $query Query to parse. 1181 * @param int $depth Optional. Number of tree levels deep we currently are. 1182 * Used to calculate indentation. 1183 * @return array { 1184 * Array containing JOIN and WHERE SQL clauses to append to a single query array. 1185 * 1186 * @type string $join SQL fragment to append to the main JOIN clause. 1187 * @type string $where SQL fragment to append to the main WHERE clause. 1188 * } 1189 */ 1190 protected function get_sql_for_query( $query, $depth = 0 ) { 1191 $sql_chunks = array( 1192 'join' => array(), 1193 'where' => array(), 1194 ); 1195 1196 $sql = array( 1197 'join' => '', 1198 'where' => '', 1199 ); 1200 1201 $indent = ''; 1202 for ( $i = 0; $i < $depth; $i++ ) { 1203 $indent .= " "; 1204 } 1205 1206 foreach ( $query as $key => $clause ) { 1207 if ( 'relation' === $key ) { 1208 $relation = $query['relation']; 1209 } else if ( is_array( $clause ) ) { 1210 1211 // This is a first-order clause. 1212 if ( $this->is_first_order_clause( $clause ) ) { 1213 $clause_sql = $this->get_sql_for_clause( $clause, $query ); 1214 1215 $where_count = count( $clause_sql['where'] ); 1216 if ( ! $where_count ) { 1217 $sql_chunks['where'][] = ''; 1218 } else if ( 1 === $where_count ) { 1219 $sql_chunks['where'][] = $clause_sql['where'][0]; 1220 } else { 1221 $sql_chunks['where'][] = '( ' . implode( ' AND ', $clause_sql['where'] ) . ' )'; 1222 } 1223 1224 $sql_chunks['join'] = array_merge( $sql_chunks['join'], $clause_sql['join'] ); 1225 // This is a subquery, so we recurse. 1226 } else { 1227 $clause_sql = $this->get_sql_for_query( $clause, $depth + 1 ); 1228 1229 $sql_chunks['where'][] = $clause_sql['where']; 1230 $sql_chunks['join'][] = $clause_sql['join']; 1231 } 1232 } 1233 } 1234 1235 // Filter to remove empties. 1236 $sql_chunks['join'] = array_filter( $sql_chunks['join'] ); 1237 $sql_chunks['where'] = array_filter( $sql_chunks['where'] ); 1238 1239 if ( empty( $relation ) ) { 1240 $relation = 'AND'; 1241 } 1242 1243 // Filter duplicate JOIN clauses and combine into a single string. 1244 if ( ! empty( $sql_chunks['join'] ) ) { 1245 $sql['join'] = implode( ' ', array_unique( $sql_chunks['join'] ) ); 1246 } 1247 1248 // Generate a single WHERE clause with proper brackets and indentation. 1249 if ( ! empty( $sql_chunks['where'] ) ) { 1250 $sql['where'] = '( ' . "\n " . $indent . implode( ' ' . "\n " . $indent . $relation . ' ' . "\n " . $indent, $sql_chunks['where'] ) . "\n" . $indent . ')'; 1251 } 1252 1253 return $sql; 1254 } 1255 1256 /** 1257 * Generate SQL JOIN and WHERE clauses for a first-order query clause. 1258 * 1259 * "First-order" means that it's an array with a 'key' or 'value'. 1260 * 1261 * @since 4.1.0 1262 * @access public 1263 * 1264 * @param array $clause Query clause. 1265 * @param array $parent_query Parent query array. 1266 * @return array { 1267 * Array containing JOIN and WHERE SQL clauses to append to a first-order query. 1268 * 1269 * @type string $join SQL fragment to append to the main JOIN clause. 1270 * @type string $where SQL fragment to append to the main WHERE clause. 1271 * } 1272 */ 1273 public function get_sql_for_clause( $clause, $parent_query ) { 1274 global $wpdb; 1275 1276 $sql_chunks = array( 1277 'where' => array(), 1278 'join' => array(), 1279 ); 1280 1281 $i = count( $this->table_aliases ); 1282 $alias = $i ? 'mt' . $i : $this->meta_table; 1283 1284 if ( isset( $clause['compare'] ) ) { 1285 $meta_compare = strtoupper( $clause['compare'] ); 1286 } else { 1287 $meta_compare = isset( $clause['value'] ) && is_array( $clause['value'] ) ? 'IN' : '='; 1288 } 1289 1290 if ( ! in_array( $meta_compare, array( 1291 '=', '!=', '>', '>=', '<', '<=', 1292 'LIKE', 'NOT LIKE', 1293 'IN', 'NOT IN', 1294 'BETWEEN', 'NOT BETWEEN', 1295 'EXISTS', 'NOT EXISTS', 1296 'REGEXP', 'NOT REGEXP', 'RLIKE' 1297 ) ) ) { 1298 $meta_compare = '='; 1299 } 1300 1301 /* 1302 * There are a number of different query structures that get 1303 * built in different ways. 1304 * 1. Key-only clauses - (a) clauses without a 'value' key that 1305 * appear in the context of an OR relation and do not use 1306 * 'NOT EXISTS' as the 'compare', or (b) clauses with an 1307 * empty array for 'value'. 1308 */ 1309 if ( ! empty( $clause['key'] ) && ( 1310 ( ! array_key_exists( 'value', $clause ) && 'NOT EXISTS' !== $meta_compare && 'OR' === $parent_query['relation'] ) || 1311 ( isset( $clause['value'] ) && is_array( $clause['value'] ) && empty( $clause['value'] ) ) 1312 ) ) { 1313 1314 $alias = $this->meta_table; 1315 $sql_chunks['join'][] = " INNER JOIN $this->meta_table ON ($this->primary_table.$this->primary_id_column = $alias.$this->meta_id_column)"; 1316 $sql_chunks['where'][] = $wpdb->prepare( "$this->meta_table.meta_key = %s", trim( $clause['key'] ) ); 1317 1318 // 2. NOT EXISTS. 1319 } else if ( 'NOT EXISTS' === $meta_compare ) { 1320 $join = " LEFT JOIN $this->meta_table"; 1321 $join .= $i ? " AS $alias" : ''; 1322 $join .= $wpdb->prepare( " ON ($this->primary_table.$this->primary_id_column = $alias.$this->meta_id_column AND $alias.meta_key = %s )", $clause['key'] ); 1323 $sql_chunks['join'][] = $join; 1324 1325 $sql_chunks['where'][] = $alias . '.' . $this->meta_id_column . ' IS NULL'; 1326 1327 // 3. EXISTS and other key-only queries. 1328 } else if ( 'EXISTS' === $meta_compare || ( ! empty( $clause['key'] ) && ! array_key_exists( 'value', $clause ) ) ) { 1329 $join = " INNER JOIN $this->meta_table"; 1330 $join .= $i ? " AS $alias" : ''; 1331 $join .= " ON ( $this->primary_table.$this->primary_id_column = $alias.$this->meta_id_column )"; 1332 $sql_chunks['join'][] = $join; 1333 1334 $sql_chunks['where'][] = $wpdb->prepare( $alias . '.meta_key = %s', trim( $clause['key'] ) ); 1335 1336 // 4. Clauses that have a value. 1337 } else if ( array_key_exists( 'value', $clause ) ) { 1338 $join = " INNER JOIN $this->meta_table"; 1339 $join .= $i ? " AS $alias" : ''; 1340 $join .= " ON ($this->primary_table.$this->primary_id_column = $alias.$this->meta_id_column)"; 1341 $sql_chunks['join'][] = $join; 1342 1343 if ( ! empty( $clause['key'] ) ) { 1344 $sql_chunks['where'][] = $wpdb->prepare( "$alias.meta_key = %s", trim( $clause['key'] ) ); 1345 } 1346 1347 $meta_type = $this->get_cast_for_type( isset( $clause['type'] ) ? $clause['type'] : '' ); 1348 1349 $meta_value = isset( $clause['value'] ) ? $clause['value'] : ''; 1350 1351 if ( in_array( $meta_compare, array( 'IN', 'NOT IN', 'BETWEEN', 'NOT BETWEEN' ) ) ) { 1352 if ( ! is_array( $meta_value ) ) { 1353 $meta_value = preg_split( '/[,\s]+/', $meta_value ); 1354 } 1355 } else { 1356 $meta_value = trim( $meta_value ); 1357 } 1358 1359 if ( 'IN' == substr( $meta_compare, -2 ) ) { 1360 $meta_compare_string = '(' . substr( str_repeat( ',%s', count( $meta_value ) ), 1 ) . ')'; 1361 } elseif ( 'BETWEEN' == substr( $meta_compare, -7 ) ) { 1362 $meta_value = array_slice( $meta_value, 0, 2 ); 1363 $meta_compare_string = '%s AND %s'; 1364 } elseif ( 'LIKE' == $meta_compare || 'NOT LIKE' == $meta_compare ) { 1365 $meta_value = '%' . $wpdb->esc_like( $meta_value ) . '%'; 1366 $meta_compare_string = '%s'; 1367 } else { 1368 $meta_compare_string = '%s'; 1369 } 1370 1371 $sql_chunks['where'][] = $wpdb->prepare( "CAST($alias.meta_value AS {$meta_type}) {$meta_compare} {$meta_compare_string}", $meta_value ); 1372 } 1373 1374 /* 1375 * Multiple WHERE clauses (for meta_key and meta_value) should 1376 * be joined in parentheses. 1377 */ 1378 if ( 1 < count( $sql_chunks['where'] ) ) { 1379 $sql_chunks['where'] = array( '( ' . implode( ' AND ', $sql_chunks['where'] ) . ' )' ); 1380 } 1381 1382 $this->table_aliases[] = $alias; 1383 1384 return $sql_chunks; 1123 1385 } 1124 1386 } -
trunk/tests/phpunit/tests/meta/query.php
r29799 r29887 41 41 ) ); 42 42 43 $this->assertSame( array( array()), $query->queries );43 $this->assertSame( array(), $query->queries ); 44 44 } 45 45 … … 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 … … 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() … … 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 … … 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 … … 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 } -
trunk/tests/phpunit/tests/post/query.php
r29857 r29887 773 773 } 774 774 775 /** 776 * @ticket 29642 777 * @group meta 778 */ 779 public function test_meta_query_nested() { 780 $p1 = $this->factory->post->create(); 781 $p2 = $this->factory->post->create(); 782 $p3 = $this->factory->post->create(); 783 784 add_post_meta( $p1, 'foo', 'bar' ); 785 add_post_meta( $p2, 'foo2', 'bar' ); 786 add_post_meta( $p3, 'foo2', 'bar' ); 787 add_post_meta( $p3, 'foo3', 'bar' ); 788 789 $query = new WP_Query( array( 790 'update_post_meta_cache' => false, 791 'update_term_meta_cache' => false, 792 'fields' => 'ids', 793 'meta_query' => array( 794 'relation' => 'OR', 795 array( 796 'key' => 'foo', 797 'value' => 'bar', 798 ), 799 array( 800 'relation' => 'AND', 801 array( 802 'key' => 'foo2', 803 'value' => 'bar', 804 ), 805 array( 806 'key' => 'foo3', 807 'value' => 'bar', 808 ), 809 ), 810 ), 811 ) ); 812 813 $expected = array( $p1, $p3 ); 814 $this->assertEqualSets( $expected, $query->posts ); 815 } 816 817 /** 818 * @ticket 29642 819 * @group meta 820 */ 821 public function test_meta_query_nested_two_levels_deep() { 822 $p1 = $this->factory->post->create(); 823 $p2 = $this->factory->post->create(); 824 $p3 = $this->factory->post->create(); 825 826 add_post_meta( $p1, 'foo', 'bar' ); 827 add_post_meta( $p3, 'foo2', 'bar' ); 828 add_post_meta( $p3, 'foo3', 'bar' ); 829 add_post_meta( $p3, 'foo4', 'bar' ); 830 831 $query = new WP_Query( array( 832 'update_post_meta_cache' => false, 833 'update_term_meta_cache' => false, 834 'fields' => 'ids', 835 'meta_query' => array( 836 'relation' => 'OR', 837 array( 838 'key' => 'foo', 839 'value' => 'bar', 840 ), 841 array( 842 'relation' => 'OR', 843 array( 844 'key' => 'foo2', 845 'value' => 'bar', 846 ), 847 array( 848 'relation' => 'AND', 849 array( 850 'key' => 'foo3', 851 'value' => 'bar', 852 ), 853 array( 854 'key' => 'foo4', 855 'value' => 'bar', 856 ), 857 ), 858 ), 859 ), 860 ) ); 861 862 $expected = array( $p1, $p3 ); 863 $this->assertEqualSets( $expected, $query->posts ); 864 } 865 866 /** 867 * @group meta 868 */ 775 869 function test_meta_between_not_between() { 776 870 $post_id = $this->factory->post->create(); … … 820 914 /** 821 915 * @ticket 16829 916 * @group meta 822 917 */ 823 918 function test_meta_default_compare() { … … 860 955 /** 861 956 * @ticket 17264 957 * @group meta 862 958 */ 863 959 function test_duplicate_posts_when_no_key() { … … 891 987 /** 892 988 * @ticket 15292 989 * @group meta 893 990 */ 894 991 function test_empty_meta_value() {
Note: See TracChangeset
for help on using the changeset viewer.