Changeset 29902
- Timestamp:
- 10/15/2014 04:39:19 PM (10 years ago)
- Location:
- trunk
- Files:
-
- 3 edited
Legend:
- Unmodified
- Added
- Removed
-
trunk/src/wp-includes/taxonomy.php
r29896 r29902 716 716 */ 717 717 public function __construct( $tax_query ) { 718 if ( isset( $tax_query['relation'] ) && strtoupper( $tax_query['relation'] ) == 'OR') {719 $this->relation = 'OR';718 if ( isset( $tax_query['relation'] ) ) { 719 $this->relation = $this->sanitize_relation( $tax_query['relation'] ); 720 720 } else { 721 721 $this->relation = 'AND'; … … 750 750 foreach ( $queries as $key => $query ) { 751 751 if ( 'relation' === $key ) { 752 $cleaned_query['relation'] = $ query;752 $cleaned_query['relation'] = $this->sanitize_relation( $query ); 753 753 754 754 // First-order clause. … … 787 787 788 788 if ( ! empty( $cleaned_subquery ) ) { 789 // All queries with children must have a relation. 790 if ( ! isset( $cleaned_subquery['relation'] ) ) { 791 $cleaned_subquery['relation'] = 'AND'; 792 } 793 789 794 $cleaned_query[] = $cleaned_subquery; 790 795 } … … 793 798 794 799 return $cleaned_query; 800 } 801 802 /** 803 * Sanitize a 'relation' operator. 804 * 805 * @since 4.1.0 806 * @access public 807 * 808 * @param string $relation Raw relation key from the query argument. 809 * @return Sanitized relation ('AND' or 'OR'). 810 */ 811 public function sanitize_relation( $relation ) { 812 if ( 'OR' === strtoupper( $relation ) ) { 813 return 'OR'; 814 } else { 815 return 'AND'; 816 } 795 817 } 796 818 … … 853 875 */ 854 876 protected function get_sql_clauses() { 855 $sql = $this->get_sql_for_query( $this->queries ); 877 /* 878 * $queries are passed by reference to get_sql_for_query() for recursion. 879 * To keep $this->queries unaltered, pass a copy. 880 */ 881 $queries = $this->queries; 882 $sql = $this->get_sql_for_query( $queries ); 856 883 857 884 if ( ! empty( $sql['where'] ) ) { … … 881 908 * } 882 909 */ 883 protected function get_sql_for_query( $query, $depth = 0 ) {910 protected function get_sql_for_query( &$query, $depth = 0 ) { 884 911 $sql_chunks = array( 885 912 'join' => array(), … … 897 924 } 898 925 899 foreach ( $query as $key => $clause ) {926 foreach ( $query as $key => &$clause ) { 900 927 if ( 'relation' === $key ) { 901 928 $relation = $query['relation']; … … 962 989 * } 963 990 */ 964 public function get_sql_for_clause( $clause, $parent_query ) {991 public function get_sql_for_clause( &$clause, $parent_query ) { 965 992 global $wpdb; 966 993 … … 989 1016 $terms = implode( ',', $terms ); 990 1017 991 $i = count( $this->table_aliases ); 992 $alias = $i ? 'tt' . $i : $wpdb->term_relationships; 993 $this->table_aliases[] = $alias; 994 995 $join .= " INNER JOIN $wpdb->term_relationships"; 996 $join .= $i ? " AS $alias" : ''; 997 $join .= " ON ($this->primary_table.$this->primary_id_column = $alias.object_id)"; 1018 /* 1019 * Before creating another table join, see if this clause has a 1020 * sibling with an existing join that can be shared. 1021 */ 1022 $alias = $this->find_compatible_table_alias( $clause, $parent_query ); 1023 if ( false === $alias ) { 1024 $i = count( $this->table_aliases ); 1025 $alias = $i ? 'tt' . $i : $wpdb->term_relationships; 1026 1027 // Store the alias as part of a flat array to build future iterators. 1028 $this->table_aliases[] = $alias; 1029 1030 // Store the alias with this clause, so later siblings can use it. 1031 $clause['alias'] = $alias; 1032 1033 $join .= " INNER JOIN $wpdb->term_relationships"; 1034 $join .= $i ? " AS $alias" : ''; 1035 $join .= " ON ($this->primary_table.$this->primary_id_column = $alias.object_id)"; 1036 } 1037 998 1038 999 1039 $where = "$alias.term_taxonomy_id $operator ($terms)"; … … 1048 1088 } 1049 1089 1090 /** 1091 * Identify an existing table alias that is compatible with the current query clause. 1092 * 1093 * We avoid unnecessary table joins by allowing each clause to look for 1094 * an existing table alias that is compatible with the query that it 1095 * needs to perform. An existing alias is compatible if (a) it is a 1096 * sibling of $clause (ie, it's under the scope of the same relation), 1097 * and (b) the combination of operator and relation between the clauses 1098 * allows for a shared table join. In the case of WP_Tax_Query, this 1099 * only applies to IN clauses that are connected by the relation OR. 1100 * 1101 * @since 4.1.0 1102 * @access protected 1103 * 1104 * @param array $clause Query clause. 1105 * @param array $parent_query Parent query of $clause. 1106 * @return string|bool Table alias if found, otherwise false. 1107 */ 1108 protected function find_compatible_table_alias( $clause, $parent_query ) { 1109 $alias = false; 1110 1111 // Sanity check. Only IN queries use the JOIN syntax . 1112 if ( ! isset( $clause['operator'] ) || 'IN' !== $clause['operator'] ) { 1113 return $alias; 1114 } 1115 1116 // Since we're only checking IN queries, we're only concerned with OR relations. 1117 if ( ! isset( $parent_query['relation'] ) || 'OR' !== $parent_query['relation'] ) { 1118 return $alias; 1119 } 1120 1121 $compatible_operators = array( 'IN' ); 1122 1123 foreach ( $parent_query as $sibling ) { 1124 if ( ! is_array( $sibling ) || ! $this->is_first_order_clause( $sibling ) ) { 1125 continue; 1126 } 1127 1128 if ( empty( $sibling['alias'] ) || empty( $sibling['operator'] ) ) { 1129 continue; 1130 } 1131 1132 // The sibling must both have compatible operator to share its alias. 1133 if ( in_array( strtoupper( $sibling['operator'] ), $compatible_operators ) ) { 1134 $alias = $sibling['alias']; 1135 break; 1136 } 1137 } 1138 1139 return $alias; 1140 } 1050 1141 1051 1142 /** -
trunk/tests/phpunit/tests/post/query.php
r29896 r29902 1377 1377 /** 1378 1378 * @group taxonomy 1379 * @ticket 18105 1380 */ 1381 public function test_tax_query_single_query_multiple_queries_operator_not_in() { 1382 $t1 = $this->factory->term->create( array( 1383 'taxonomy' => 'category', 1384 'slug' => 'foo', 1385 'name' => 'Foo', 1386 ) ); 1387 $t2 = $this->factory->term->create( array( 1388 'taxonomy' => 'category', 1389 'slug' => 'bar', 1390 'name' => 'Bar', 1391 ) ); 1392 $p1 = $this->factory->post->create(); 1393 $p2 = $this->factory->post->create(); 1394 $p3 = $this->factory->post->create(); 1395 1396 wp_set_post_terms( $p1, $t1, 'category' ); 1397 wp_set_post_terms( $p2, $t2, 'category' ); 1398 1399 $q = new WP_Query( array( 1400 'fields' => 'ids', 1401 'update_post_meta_cache' => false, 1402 'update_post_term_cache' => false, 1403 'tax_query' => array( 1404 'relation' => 'AND', 1405 array( 1406 'taxonomy' => 'category', 1407 'terms' => array( 'foo' ), 1408 'field' => 'slug', 1409 'operator' => 'NOT IN', 1410 ), 1411 array( 1412 'taxonomy' => 'category', 1413 'terms' => array( 'bar' ), 1414 'field' => 'slug', 1415 'operator' => 'NOT IN', 1416 ), 1417 ), 1418 ) ); 1419 1420 $this->assertEquals( array( $p3 ), $q->posts ); 1421 } 1422 1423 /** 1424 * @group taxonomy 1379 1425 */ 1380 1426 public function test_tax_query_single_query_multiple_terms_operator_and() { -
trunk/tests/phpunit/tests/term/query.php
r29805 r29902 219 219 $this->assertTrue( is_wp_error( $tq->queries[0] ) ); 220 220 } 221 222 /** 223 * @ticket 18105 224 */ 225 public function test_get_sql_relation_or_operator_in() { 226 register_taxonomy( 'wptests_tax', 'post' ); 227 228 $t1 = $this->factory->term->create( array( 229 'taxonomy' => 'wptests_tax', 230 ) ); 231 $t2 = $this->factory->term->create( array( 232 'taxonomy' => 'wptests_tax', 233 ) ); 234 $t3 = $this->factory->term->create( array( 235 'taxonomy' => 'wptests_tax', 236 ) ); 237 238 $tq = new WP_Tax_Query( array( 239 'relation' => 'OR', 240 array( 241 'taxonomy' => 'wptests_tax', 242 'field' => 'term_id', 243 'terms' => $t1, 244 ), 245 array( 246 'taxonomy' => 'wptests_tax', 247 'field' => 'term_id', 248 'terms' => $t2, 249 ), 250 array( 251 'taxonomy' => 'wptests_tax', 252 'field' => 'term_id', 253 'terms' => $t3, 254 ), 255 ) ); 256 257 global $wpdb; 258 $sql = $tq->get_sql( $wpdb->posts, 'ID' ); 259 260 // Only one JOIN is required with OR + IN. 261 $this->assertSame( 1, substr_count( $sql['join'], 'JOIN' ) ); 262 263 _unregister_taxonomy( 'wptests_tax' ); 264 } 265 266 /** 267 * @ticket 18105 268 */ 269 public function test_get_sql_relation_and_operator_in() { 270 register_taxonomy( 'wptests_tax', 'post' ); 271 272 $t1 = $this->factory->term->create( array( 273 'taxonomy' => 'wptests_tax', 274 ) ); 275 $t2 = $this->factory->term->create( array( 276 'taxonomy' => 'wptests_tax', 277 ) ); 278 $t3 = $this->factory->term->create( array( 279 'taxonomy' => 'wptests_tax', 280 ) ); 281 282 $tq = new WP_Tax_Query( array( 283 'relation' => 'AND', 284 array( 285 'taxonomy' => 'wptests_tax', 286 'field' => 'term_id', 287 'terms' => $t1, 288 ), 289 array( 290 'taxonomy' => 'wptests_tax', 291 'field' => 'term_id', 292 'terms' => $t2, 293 ), 294 array( 295 'taxonomy' => 'wptests_tax', 296 'field' => 'term_id', 297 'terms' => $t3, 298 ), 299 ) ); 300 301 global $wpdb; 302 $sql = $tq->get_sql( $wpdb->posts, 'ID' ); 303 304 $this->assertSame( 3, substr_count( $sql['join'], 'JOIN' ) ); 305 306 _unregister_taxonomy( 'wptests_tax' ); 307 } 308 309 /** 310 * @ticket 18105 311 */ 312 public function test_get_sql_nested_relation_or_operator_in() { 313 register_taxonomy( 'wptests_tax', 'post' ); 314 315 $t1 = $this->factory->term->create( array( 316 'taxonomy' => 'wptests_tax', 317 ) ); 318 $t2 = $this->factory->term->create( array( 319 'taxonomy' => 'wptests_tax', 320 ) ); 321 $t3 = $this->factory->term->create( array( 322 'taxonomy' => 'wptests_tax', 323 ) ); 324 325 $tq = new WP_Tax_Query( array( 326 'relation' => 'OR', 327 array( 328 'taxonomy' => 'wptests_tax', 329 'field' => 'term_id', 330 'terms' => $t1, 331 ), 332 array( 333 'relation' => 'OR', 334 array( 335 'taxonomy' => 'wptests_tax', 336 'field' => 'term_id', 337 'terms' => $t2, 338 ), 339 array( 340 'taxonomy' => 'wptests_tax', 341 'field' => 'term_id', 342 'terms' => $t3, 343 ), 344 ), 345 ) ); 346 347 global $wpdb; 348 $sql = $tq->get_sql( $wpdb->posts, 'ID' ); 349 350 $this->assertSame( 2, substr_count( $sql['join'], 'JOIN' ) ); 351 352 _unregister_taxonomy( 'wptests_tax' ); 353 } 354 221 355 }
Note: See TracChangeset
for help on using the changeset viewer.