Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support order by post__in, post_parent__in and post_name__in #51

Open
wants to merge 20 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 7 additions & 4 deletions .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,6 @@ language: php

matrix:
include:
- php: 5.3
env: WP_VERSION=4.3.3 ES_VERSION=2.2.0
- php: 5.3
env: WP_VERSION=latest ES_VERSION=2.2.0
- php: 5.6
env: WP_VERSION=latest ES_VERSION=2.2.0
- php: 7.0
Expand All @@ -27,6 +23,13 @@ before_install:
- sudo service elasticsearch start

before_script:
- export PATH="$HOME/.composer/vendor/bin:$PATH"
- |
if [[ ${TRAVIS_PHP_VERSION:0:2} == "7." ]]; then
composer global require "phpunit/phpunit=5.7.*"
else
composer global require "phpunit/phpunit=4.8.*"
fi
- bash bin/install-wp-tests.sh wordpress_test root '' localhost $WP_VERSION
- sleep 5 # ensure ES is running

Expand Down
3 changes: 2 additions & 1 deletion adapters/searchpress.php
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ protected function query_es( $es_args ) {

function sp_es_field_map( $es_map ) {
return wp_parse_args( array(
'ID' => 'post_id',
'post_name' => 'post_name.raw',
'post_title' => 'post_title.raw',
'post_title.analyzed' => 'post_title',
Expand Down Expand Up @@ -74,4 +75,4 @@ function es_wp_query_index_test_data() {
SP_API()->post( '_refresh' );
}

}
}
1 change: 1 addition & 0 deletions adapters/travis.php
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ protected function query_es( $es_args ) {

function travis_es_field_map( $es_map ) {
return wp_parse_args( array(
'ID' => 'post_id',
'post_meta' => 'post_meta.%s.value',
'post_author' => 'post_author.user_id',
'post_date' => 'post_date.date',
Expand Down
16 changes: 10 additions & 6 deletions adapters/wpcom-vip.php
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@ protected function set_posts( $q, $es_response ) {
$post_id = (array) $hit['fields'][ $this->es_map( 'post_id' ) ];
$this->posts[] = reset( $post_id );
}

$this->posts = $this->post_query_sort_handler( $this->posts, $q );
return;

case 'id=>parent' :
Expand All @@ -33,7 +35,6 @@ protected function set_posts( $q, $es_response ) {
default :
if ( apply_filters( 'es_query_use_source', false ) ) {
$this->posts = wp_list_pluck( $es_response['results']['hits'], '_source' );
return;
} else {
$post_ids = array();
foreach ( $es_response['results']['hits'] as $hit ) {
Expand All @@ -46,8 +47,10 @@ protected function set_posts( $q, $es_response ) {
$post__in = implode( ',', $post_ids );
$this->posts = $wpdb->get_results( "SELECT $wpdb->posts.* FROM $wpdb->posts WHERE ID IN ($post__in) ORDER BY FIELD( {$wpdb->posts}.ID, $post__in )" );
}
return;
}

$this->posts = $this->post_query_sort_handler( $this->posts, $q );
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For consistency with the rest of the switch, I would add a return; after this statement.

return;
}
} else {
$this->posts = array();
Expand All @@ -73,6 +76,7 @@ public function set_found_posts( $q, $es_response ) {

function vip_es_field_map( $es_map ) {
return wp_parse_args( array(
'ID' => 'post_id',
'post_author' => 'author_id',
'post_author.user_nicename' => 'author_login',
'post_date' => 'date',
Expand Down Expand Up @@ -100,8 +104,8 @@ function vip_es_field_map( $es_map ) {
'post_title' => 'title',
'post_title.analyzed' => 'title',
'post_excerpt' => 'excerpt',
'post_password' => 'post_password', // this isn't indexed on vip
'post_name' => 'post_name', // this isn't indexed on vip
'post_password' => 'post_password', // this isn't indexed on vip
'post_name' => 'slug',
'post_modified' => 'modified',
'post_modified.year' => 'modified_token.year',
'post_modified.month' => 'modified_token.month',
Expand All @@ -123,9 +127,9 @@ function vip_es_field_map( $es_map ) {
'post_modified_gmt.minute' => 'modified_gmt_token.minute',
'post_modified_gmt.second' => 'modified_gmt_token.second',
'post_parent' => 'parent_post_id',
'menu_order' => 'menu_order', // this isn't indexed on vip
'menu_order' => 'menu_order',
'post_mime_type' => 'post_mime_type', // this isn't indexed on vip
'comment_count' => 'comment_count', // this isn't indexed on vip
'comment_count' => 'discussion.comment_count',
'post_meta' => 'meta.%s.value.raw_lc',
'post_meta.analyzed' => 'meta.%s.value',
'post_meta.long' => 'meta.%s.long',
Expand Down
2 changes: 1 addition & 1 deletion class-es-wp-query-shoehorn.php
Original file line number Diff line number Diff line change
Expand Up @@ -219,4 +219,4 @@ private function reboot_query_vars( &$query ) {
$q['posts_per_page'] = get_option( 'posts_per_page' );
}
}
}
}
90 changes: 82 additions & 8 deletions class-es-wp-query-wrapper.php
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,8 @@ protected function set_posts( $q, $es_response ) {
$post_id = (array) $hit['fields'][ $this->es_map( 'post_id' ) ];
$this->posts[] = reset( $post_id );
}

$this->posts = $this->post_query_sort_handler( $this->posts, $q );
return;

case 'id=>parent' :
Expand All @@ -60,7 +62,6 @@ protected function set_posts( $q, $es_response ) {
default :
if ( apply_filters( 'es_query_use_source', false ) ) {
$this->posts = wp_list_pluck( $es_response['hits']['hits'], '_source' );
return;
} else {
$post_ids = array();
foreach ( $es_response['hits']['hits'] as $hit ) {
Expand All @@ -73,14 +74,88 @@ protected function set_posts( $q, $es_response ) {
$post__in = implode( ',', $post_ids );
$this->posts = $wpdb->get_results( "SELECT $wpdb->posts.* FROM $wpdb->posts WHERE ID IN ($post__in) ORDER BY FIELD( {$wpdb->posts}.ID, $post__in )" );
}
return;
}

$this->posts = $this->post_query_sort_handler( $this->posts, $q );
}
} else {
$this->posts = array();
}
}

/**
* Post query sort handler
* Handle sorting by `post__in`, `post__in` and `post_parent__in`.
*
* @param array $posts Query result posts.
* @param array $query Initial query.
* @return array Sorted posts.
*/
protected function post_query_sort_handler( $posts, $query ) {
if ( empty( $query['orderby'] ) ) {
return $posts;
}

// Determine the key to sort by.
switch ( $query['orderby'] ) {
case 'post__in' :
$key = 'ID';
break;

case 'post_name__in' :
$key = 'post_name';
break;

case 'post_parent__in' :
$key = 'post_parent';
break;

default :
return $posts;
}

// Flip the order to allow retrieval by index.
$order = array_flip( $query[ $query['orderby'] ] );

// Support raw Elasticsearch documents.
$use_source = apply_filters( 'es_query_use_source', false );
if ( $use_source ) {
$key = $this->es_map( $key );
}

usort( $posts, function( $a, $b ) use ( $order, $key, $use_source ) {
// Add support for using the Elasticsearch _source field.
if ( $use_source ) {
// Cast the array to object to mock the `WP_Post` object.
$a = (object) $a;
$b = (object) $b;
} else {
// Add support for a query of only post ID fields.
if ( ! ( $a instanceof WP_Post ) ) {
$a = get_post( $a );
}

if ( ! ( $b instanceof WP_Post ) ) {
$b = get_post( $b );
}
}

if (
! isset( $a->$key ) || ! isset( $b->$key )
|| ! isset( $order[ $a->$key ] ) || ! isset( $order[ $b->$key ] ) ) {
return 0;
}

if ( $order[ $a->$key ] === $order[ $b->$key ] ) {
return 0;
} else {
return $order[ $a->$key ] < $order[ $b->$key ] ? -1 : 1;
}
} );

return $posts;
}

// @todo: Core queries where 1=0 here, which probably happens for good reason.
// We're just going to abandon ship for now, but if it causes issues we'll switch
// to a mysql query where 1=0
Expand Down Expand Up @@ -462,6 +537,9 @@ public function get_posts() {
$q['attachment'] = sanitize_title_for_query( wp_basename( $q['attachment'] ) );
$q['name'] = $q['attachment'];
$filter[] = $this->dsl_terms( $this->es_map( 'post_name' ), $q['attachment'] );
} elseif ( ! empty( $q['post_name__in'] ) ) {
$post_name__in = $q['post_name__in'];
$filter[] = $this->dsl_terms( $this->es_map( 'post_name' ), $post_name__in );
}


Expand Down Expand Up @@ -682,12 +760,8 @@ public function get_posts() {
}
} elseif ( 'none' == $q['orderby'] ) {
// nothing to see here
} elseif ( $q['orderby'] == 'post__in' && ! empty( $post__in ) ) {
// @todo: Figure this out... Elasticsearch doesn't have an equivalent of this
// $orderby = "FIELD( {$wpdb->posts}.ID, $post__in )";
} elseif ( $q['orderby'] == 'post_parent__in' && ! empty( $post_parent__in ) ) {
// (see above)
// $orderby = "FIELD( {$wpdb->posts}.post_parent, $post_parent__in )";
} elseif ( in_array( $q['orderby'], array( 'post__in', 'post_parent__in', 'post_name__in' ) ) ) {
// Handled post-query by `ES_WP_Query_Wrapper::post_query_sort_handler()`.
} else {
if ( is_array( $q['orderby'] ) ) {
foreach ( $q['orderby'] as $_orderby => $order ) {
Expand Down
143 changes: 143 additions & 0 deletions tests/query/query.php
Original file line number Diff line number Diff line change
Expand Up @@ -67,4 +67,147 @@ function test_orderby() {
$q5 = new ES_WP_Query( array( 'orderby' => array() ) );
$this->assertFalse( isset( $q5->es_args['sort'] ) );
}

function test_orderby_post__in() {
$p_a = $this->factory->post->create();
$p_b = $this->factory->post->create();
$p_c = $this->factory->post->create();
$p_d = $this->factory->post->create();
es_wp_query_index_test_data();

$post__in = [
$p_c,
$p_a,
$p_d,
$p_b,
];

$q = new ES_WP_Query( [
'post__in' => $post__in,
'orderby' => 'post__in',
'order' => 'ASC',
'posts_per_page' => 4,
] );

$this->assertNotEmpty( $q->posts );

// Verify that the post is in the proper array.
foreach ( $q->posts as $post ) {
$this->assertTrue( in_array( $post->ID, $post__in, true ) );
}

// Assert that the order matches
foreach ( $post__in as $i => $post_ID ) {
$this->assertEquals(
$post_ID,
$q->posts[ $i ]->ID,
'Post not in expected order from `post__in`.'
);
}

// Query only the ID field.
$q2 = new ES_WP_Query( [
'post__in' => $post__in,
'orderby' => 'post__in',
'order' => 'ASC',
'posts_per_page' => 4,
'fields' => 'ids',
] );

$this->assertNotEmpty( $q2->posts );

// Verify that the post is in the proper array.
foreach ( $q2->posts as $post ) {
$this->assertTrue( in_array( $post, $post__in, true ) );
}

// Assert that the order matches
foreach ( $post__in as $i => $post_ID ) {
$this->assertEquals(
$post_ID,
$q2->posts[ $i ],
'Post not in expected order from `post__in`.'
);
}
}

function test_post_name__in() {
$post_a = $this->factory->post->create( [ 'post_name' => 'post-a' ] );
$post_b = $this->factory->post->create( [ 'post_name' => 'post-b' ] );
$post_c = $this->factory->post->create( [ 'post_name' => 'post-c' ] );
es_wp_query_index_test_data();

$post_name__in = [
'post-c',
'post-a',
'post-b',
];

$q = new ES_WP_Query( [
'post_name__in' => $post_name__in,
'orderby' => 'post_name__in',
'order' => 'ASC',
] );

$this->assertNotEmpty( $q->posts );

// Verify that the post name is in the proper array.
foreach ( $q->posts as $post ) {
$this->assertTrue( in_array( $post->post_name, $post_name__in, true ) );
}

// Assert that the order matches.
foreach ( $post_name__in as $i => $post_name ) {
$this->assertEquals(
$post_name,
$q->posts[ $i ]->post_name,
'Post not in expected order from `post_name__in`.'
);
}
}

public function test_post_parent__in() {
$parent_a = $this->factory->post->create();
$parent_b = $this->factory->post->create();
$parent_c = $this->factory->post->create();
$children = [];

foreach( [ $parent_a, $parent_b, $parent_c ] as $parent ) {
$children[ $parent ] = $this->factory->post->create( [ 'post_parent' => $parent ] );
}

es_wp_query_index_test_data();

$post_parent__in = [
$parent_b,
$parent_a,
$parent_c,
];

$q = new ES_WP_Query( [
'post_parent__in' => $post_parent__in,
'orderby' => 'post_parent__in',
'order' => 'ASC',
] );

$this->assertNotEmpty( $q->posts );

// Verify that the post is in the proper children array.
foreach ( $q->posts as $post ) {
$this->assertEquals(
$post->ID,
$children[ $post->post_parent ],
'Unexpected post returned from `post_parent__in`.'
);
}

// Verify the order of `post_parent__in`.
foreach ( $post_parent__in as $i => $post_parent_id ) {
$this->assertEquals(
$post_parent_id,
$q->posts[ $i ]->post_parent,
'Post not in expected order from `post_parent__in`.'
);
}
}
}