Search code examples
phpsymfonyelasticsearchfoselasticabundle

FOSElacticaBundle: failed to parse [geo_bbox] query. unexpected field [gte] [reason: all shards failed]


I use FOSElasticaBundle's GeoBoundingBox [1] class to successfully populate a map of users in a given area. Now I want to add a criterion in order to only map users who were active within the last 10 minutes. So I add a mapping in fos_elastica.yml like

types:
    user:
        mappings:
            id: ~
            username: ~
            firstName: ~
            lastName: ~
            email: ~
            location:
                type: geo_point
            lastActivityAt:
                type: date
...

I run fos:elastica:reset and fos:elastica:populate, just to be sure I have current results. Those commands run successfully. So far so good, I also clear the Symfony cache. I then add the following logic in the controller that I'm hitting with an AJAX call

$query = new GeoBoundingBox(
            'location',
            array(
                //top_left
                array(
                   'lat' => $data['north'],
                   'lon' => $data['west']
                ),
                //bottom_right
                array(
                    'lat' => $data['south'],
                    'lon' => $data['east']
                )
            )
        );

$earlier = new \DateTime('10 minutes ago');
$query->addParam('lastActivityAt', ['gte' => $earlier]);

$result = $this->get('fos_elastica.finder.app.user')->find($query);
...

That's where the trouble starts. I refresh the map in the browser, and in the network inspector, I now get the following error

failed to parse [geo_bbox] query. unexpected field [gte] [reason: all shards failed]

I'm clearly doing something wrong here. I don't know much about Elasticsearch. How can I correctly use Param::addParam() to set this up?

[1] https://github.com/FriendsOfSymfony/FOSElasticaBundle


Solution

  • As the error message suggests, you cannot add a datetime range query to a geo_bounding_box query. You'll need to wrap both in a so-called boolean query.

    You'd need something along the lines of:

    $boolQuery = new \Elastica\Query\BoolQuery();
    
    $bboxQuery = new GeoBoundingBox(
                'location',
                array(
                    //top_left
                    array(
                       'lat' => $data['north'],
                       'lon' => $data['west']
                    ),
                    //bottom_right
                    array(
                        'lat' => $data['south'],
                        'lon' => $data['east']
                    )
                )
            );
    $boolQuery->addMust($bboxQuery);
    
    $rangeQuery = new Elastica\Query\Range();
            $earlier = new \DateTime('2 hours ago');
            $earlierString = substr($earlier->format(DateTimeInterface::ISO8601), 0, -5);
            $timeZone = substr($earlier->format(DateTimeInterface::ISO8601), -5, 5);
            $rangeQuery->addField(
                'lastActivityAt',
                [
                    'time_zone'=> $timeZone,
                    'gte' => $earlierString,
                    'lte' => 'now'
                ]
            );
    $boolQuery->addMust($rangeQuery);
    
    $result = $this->get('fos_elastica.finder.app.user')->find($boolQuery);