Introduce support for nested queries in WP_Meta_Query.

Previously, meta query arguments could be joined by a single AND or OR relation.
Now, these queries can be arbitrarily nested, allowing clauses to be linked
together with multiple relations.

Adds unit tests for the new nesting syntax. Modifies a few existing unit tests
that were overly specific for the old SQL syntax. Backward compatibility with
existing syntax is fully maintained.

Props boonebgorges, DrewAPicture.
See #29642.

git-svn-id: https://develop.svn.wordpress.org/trunk@29887 602fd350-edb4-49c9-b593-d223f7449a82
This commit is contained in:
Boone Gorges
2014-10-13 22:02:18 +00:00
parent f97a5aee3a
commit 0b05366c6f
3 changed files with 782 additions and 173 deletions

View File

@@ -40,7 +40,7 @@ class Tests_Meta_Query extends WP_UnitTestCase {
array(),
) );
$this->assertSame( array( array() ), $query->queries );
$this->assertSame( array(), $query->queries );
}
/**
@@ -153,19 +153,44 @@ class Tests_Meta_Query extends WP_UnitTestCase {
$query = new WP_Meta_Query();
// just meta_value
$query->parse_query_vars( array( 'meta_key' => 'abc' ) );
$this->assertEquals( array( array( 'key' => 'abc' ) ), $query->queries );
$expected = array(
'relation' => 'OR',
array(
'key' => 'abc',
),
);
$query->parse_query_vars( array(
'meta_key' => 'abc',
) );
$this->assertEquals( $expected, $query->queries );
// meta_key & meta_value
$query->parse_query_vars( array( 'meta_key' => 'abc', 'meta_value' => 'def' ) );
$this->assertEquals( array( array( 'key' => 'abc', 'value' => 'def' ) ), $query->queries );
$expected = array(
'relation' => 'OR',
array(
'key' => 'abc',
'value' => 'def',
),
);
$query->parse_query_vars( array(
'meta_key' => 'abc',
'meta_value' => 'def',
) );
$this->assertEquals( $expected, $query->queries );
// meta_compare
$query->parse_query_vars( array( 'meta_key' => 'abc', 'meta_compare' => '=>' ) );
$this->assertEquals( array( array( 'key' => 'abc', 'compare' => '=>' ) ), $query->queries );
$expected = array(
'relation' => 'OR',
array(
'key' => 'abc',
'compare' => '=>',
),
);
$query->parse_query_vars( array(
'meta_key' => 'abc',
'meta_compare' => '=>',
) );
$this->assertEquals( $expected, $query->queries );
}
/**
@@ -202,6 +227,229 @@ class Tests_Meta_Query extends WP_UnitTestCase {
$this->assertEquals( 'CHAR', $query->get_cast_for_type( 'ANYTHING ELSE' ) );
}
public function test_sanitize_query_single_query() {
$expected = array(
'relation' => 'OR',
array(
'key' => 'foo',
'value' => 'bar',
),
);
$q = new WP_Meta_Query();
$found = $q->sanitize_query( array(
array(
'key' => 'foo',
'value' => 'bar',
),
) );
$this->assertEquals( $expected, $found );
}
public function test_sanitize_query_multiple_first_order_queries_relation_default() {
$expected = array(
'relation' => 'AND',
array(
'key' => 'foo',
'value' => 'bar',
),
array(
'key' => 'foo2',
'value' => 'bar2',
),
);
$q = new WP_Meta_Query();
$found = $q->sanitize_query( array(
array(
'key' => 'foo',
'value' => 'bar',
),
array(
'key' => 'foo2',
'value' => 'bar2',
),
) );
$this->assertEquals( $expected, $found );
}
public function test_sanitize_query_multiple_first_order_queries_relation_or() {
$expected = array(
'relation' => 'OR',
array(
'key' => 'foo',
'value' => 'bar',
),
array(
'key' => 'foo2',
'value' => 'bar2',
),
);
$q = new WP_Meta_Query();
$found = $q->sanitize_query( array(
'relation' => 'OR',
array(
'key' => 'foo',
'value' => 'bar',
),
array(
'key' => 'foo2',
'value' => 'bar2',
),
) );
$this->assertEquals( $expected, $found );
}
public function test_sanitize_query_multiple_first_order_queries_relation_or_lowercase() {
$expected = array(
'relation' => 'OR',
array(
'key' => 'foo',
'value' => 'bar',
),
array(
'key' => 'foo2',
'value' => 'bar2',
),
);
$q = new WP_Meta_Query();
$found = $q->sanitize_query( array(
'relation' => 'or',
array(
'key' => 'foo',
'value' => 'bar',
),
array(
'key' => 'foo2',
'value' => 'bar2',
),
) );
$this->assertEquals( $expected, $found );
}
public function test_sanitize_query_multiple_first_order_queries_invalid_relation() {
$expected = array(
'relation' => 'AND',
array(
'key' => 'foo',
'value' => 'bar',
),
array(
'key' => 'foo2',
'value' => 'bar2',
),
);
$q = new WP_Meta_Query();
$found = $q->sanitize_query( array(
'relation' => 'FOO',
array(
'key' => 'foo',
'value' => 'bar',
),
array(
'key' => 'foo2',
'value' => 'bar2',
),
) );
$this->assertEquals( $expected, $found );
}
public function test_sanitize_query_single_query_which_is_a_nested_query() {
$expected = array(
'relation' => 'OR',
array(
'relation' => 'AND',
array(
'key' => 'foo',
'value' => 'bar',
),
array(
'key' => 'foo2',
'value' => 'bar2',
),
)
);
$q = new WP_Meta_Query();
$found = $q->sanitize_query( array(
array(
array(
'key' => 'foo',
'value' => 'bar',
),
array(
'key' => 'foo2',
'value' => 'bar2',
),
),
) );
$this->assertEquals( $expected, $found );
}
public function test_sanitize_query_multiple_nested_queries() {
$expected = array(
'relation' => 'OR',
array(
'relation' => 'AND',
array(
'key' => 'foo',
'value' => 'bar',
),
array(
'key' => 'foo2',
'value' => 'bar2',
),
),
array(
'relation' => 'AND',
array(
'key' => 'foo3',
'value' => 'bar3',
),
array(
'key' => 'foo4',
'value' => 'bar4',
),
),
);
$q = new WP_Meta_Query();
$found = $q->sanitize_query( array(
'relation' => 'OR',
array(
array(
'key' => 'foo',
'value' => 'bar',
),
array(
'key' => 'foo2',
'value' => 'bar2',
),
),
array(
array(
'key' => 'foo3',
'value' => 'bar3',
),
array(
'key' => 'foo4',
'value' => 'bar4',
),
),
) );
$this->assertEquals( $expected, $found );
}
/**
* Invalid $type will fail to get a table from _get_meta_table()
*/
@@ -229,7 +477,7 @@ class Tests_Meta_Query extends WP_UnitTestCase {
$sql = $query->get_sql( 'post', $wpdb->posts, 'ID', $this );
// We should have 2 joins - one for my_first_key and one for my_second_key
$this->assertEquals( 2, substr_count( $sql['join'], 'INNER JOIN' ) );
$this->assertEquals( 2, substr_count( $sql['join'], 'JOIN' ) );
// The WHERE should check my_third_key against an unaliased table
$this->assertEquals( 1, substr_count( $sql['where'], "$wpdb->postmeta.meta_key = 'my_third_key'" ) );
@@ -247,7 +495,7 @@ class Tests_Meta_Query extends WP_UnitTestCase {
) );
$sql = $query->get_sql( 'post', $wpdb->posts, 'ID', $this );
$this->assertEquals( 1, substr_count( $sql['where'], "CAST($wpdb->postmeta.meta_value AS CHAR) = '')" ) );
$this->assertEquals( 1, substr_count( $sql['where'], "CAST($wpdb->postmeta.meta_value AS CHAR) = ''" ) );
}
/**
@@ -558,7 +806,9 @@ class Tests_Meta_Query extends WP_UnitTestCase {
) );
$sql = $query->get_sql( 'post', $wpdb->posts, 'ID', $this );
$this->assertContains( "{$wpdb->postmeta}.meta_key = 'exclude'\nOR", $sql['where'] );
// Use regex because we don't care about the whitespace before OR.
$this->assertRegExp( "/{$wpdb->postmeta}\.meta_key = \'exclude\'\s+OR/", $sql['where'] );
$this->assertNotContains( "{$wpdb->postmeta}.post_id IS NULL", $sql['where'] );
}
}

View File

@@ -772,6 +772,100 @@ class Tests_Post_Query extends WP_UnitTestCase {
$this->assertEqualSets( array( $post_4, $post_3, $post_2, $post_1 ), $query->posts );
}
/**
* @ticket 29642
* @group meta
*/
public function test_meta_query_nested() {
$p1 = $this->factory->post->create();
$p2 = $this->factory->post->create();
$p3 = $this->factory->post->create();
add_post_meta( $p1, 'foo', 'bar' );
add_post_meta( $p2, 'foo2', 'bar' );
add_post_meta( $p3, 'foo2', 'bar' );
add_post_meta( $p3, 'foo3', 'bar' );
$query = new WP_Query( array(
'update_post_meta_cache' => false,
'update_term_meta_cache' => false,
'fields' => 'ids',
'meta_query' => array(
'relation' => 'OR',
array(
'key' => 'foo',
'value' => 'bar',
),
array(
'relation' => 'AND',
array(
'key' => 'foo2',
'value' => 'bar',
),
array(
'key' => 'foo3',
'value' => 'bar',
),
),
),
) );
$expected = array( $p1, $p3 );
$this->assertEqualSets( $expected, $query->posts );
}
/**
* @ticket 29642
* @group meta
*/
public function test_meta_query_nested_two_levels_deep() {
$p1 = $this->factory->post->create();
$p2 = $this->factory->post->create();
$p3 = $this->factory->post->create();
add_post_meta( $p1, 'foo', 'bar' );
add_post_meta( $p3, 'foo2', 'bar' );
add_post_meta( $p3, 'foo3', 'bar' );
add_post_meta( $p3, 'foo4', 'bar' );
$query = new WP_Query( array(
'update_post_meta_cache' => false,
'update_term_meta_cache' => false,
'fields' => 'ids',
'meta_query' => array(
'relation' => 'OR',
array(
'key' => 'foo',
'value' => 'bar',
),
array(
'relation' => 'OR',
array(
'key' => 'foo2',
'value' => 'bar',
),
array(
'relation' => 'AND',
array(
'key' => 'foo3',
'value' => 'bar',
),
array(
'key' => 'foo4',
'value' => 'bar',
),
),
),
),
) );
$expected = array( $p1, $p3 );
$this->assertEqualSets( $expected, $query->posts );
}
/**
* @group meta
*/
function test_meta_between_not_between() {
$post_id = $this->factory->post->create();
add_post_meta( $post_id, 'time', 500 );
@@ -819,6 +913,7 @@ class Tests_Post_Query extends WP_UnitTestCase {
/**
* @ticket 16829
* @group meta
*/
function test_meta_default_compare() {
// compare should default to IN when meta_value is an array
@@ -859,6 +954,7 @@ class Tests_Post_Query extends WP_UnitTestCase {
/**
* @ticket 17264
* @group meta
*/
function test_duplicate_posts_when_no_key() {
$post_id = $this->factory->post->create();
@@ -890,6 +986,7 @@ class Tests_Post_Query extends WP_UnitTestCase {
/**
* @ticket 15292
* @group meta
*/
function test_empty_meta_value() {
$post_id = $this->factory->post->create();