Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Added Elastic::Model::SearchBuilder to handle easy serialization of $…
…docs or $uids into search params
- Loading branch information
1 parent
179116e
commit 79e9aa3
Showing
13 changed files
with
5,506 additions
and
6 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Original file line | Diff line number | Diff line change |
---|---|---|---|
@@ -0,0 +1,245 @@ | |||
package Elastic::Model::SearchBuilder; | |||
|
|||
use strict; | |||
use warnings; | |||
use parent 'ElasticSearch::SearchBuilder'; | |||
use Carp; | |||
|
|||
#=================================== | |||
sub _top_ElasticDocREF { | |||
#=================================== | |||
my ( $self, $type, $doc ) = @_; | |||
$self->_uid_to_terms( $type, '', $doc->uid ); | |||
} | |||
|
|||
#=================================== | |||
sub _top_ElasticUIDREF { | |||
#=================================== | |||
my ( $self, $type, $uid ) = @_; | |||
$self->_uid_to_terms( $type, '', $uid ); | |||
} | |||
|
|||
#=================================== | |||
sub _hashpair_ElasticDocREF { | |||
#=================================== | |||
my ( $self, $type, $k, $v ) = @_; | |||
$self->_uid_to_terms( $type, $k, $v->uid ); | |||
} | |||
|
|||
#=================================== | |||
sub _hashpair_ElasticUIDREF { | |||
#=================================== | |||
my ( $self, $type, $k, $v ) = @_; | |||
$self->_uid_to_terms( $type, $k, $v ); | |||
} | |||
|
|||
#=================================== | |||
sub _filter_field_terms { | |||
#=================================== | |||
my ( $self, $k, $op, $val ) = @_; | |||
|
|||
return $self->_SWITCH_refkind( | |||
"Filter field operator -$op", | |||
$val, | |||
{ ElasticDocREF => | |||
sub { $self->_uid_to_terms( 'filter', $k, $val->uid ) }, | |||
ElasticUIDREF => | |||
sub { $self->_uid_to_terms( 'filter', $k, $val ) }, | |||
FALLBACK => sub { | |||
$self->SUPER::_filter_field_terms( $k, $op, $val ); | |||
}, | |||
} | |||
); | |||
} | |||
|
|||
#=================================== | |||
sub _query_field_text { | |||
#=================================== | |||
my $self = shift; | |||
my ( $k, $op, $val ) = @_; | |||
|
|||
return $self->_SWITCH_refkind( | |||
"Query field operator -$op", | |||
$val, | |||
{ ElasticDocREF => | |||
sub { $self->_uid_to_terms( 'query', $k, $val->uid ) }, | |||
ElasticUIDREF => | |||
sub { $self->_uid_to_terms( 'query', $k, $val ) }, | |||
FALLBACK => sub { | |||
$self->SUPER::_query_field_text( $k, $op, $val ); | |||
}, | |||
} | |||
); | |||
} | |||
|
|||
#=================================== | |||
sub _uid_to_terms { | |||
#=================================== | |||
my ( $self, $type, $k, $uid ) = @_; | |||
$k = length $k ? $k . '.' : ''; | |||
my @clauses; | |||
for (qw(index type id)) { | |||
my $val = $uid->$_ or croak "UID missing ($_)"; | |||
push @clauses, { term => { "${k}uid.$_" => $val } }; | |||
} | |||
return $type eq 'query' | |||
? { bool => { must => \@clauses } } | |||
: { and => \@clauses } | |||
|
|||
} | |||
|
|||
#=================================== | |||
sub _refkind { | |||
#=================================== | |||
my ( $self, $data ) = @_; | |||
|
|||
return 'UNDEF' unless defined $data; | |||
|
|||
my $ref | |||
= !Scalar::Util::blessed($data) ? ref $data | |||
: !$data->can('does') ? '' | |||
: $data->does('Elastic::Model::Role::Doc') ? 'ElasticDoc' | |||
: $data->isa('Elastic::Model::UID') ? 'ElasticUID' | |||
: ''; | |||
|
|||
return 'SCALAR' unless $ref; | |||
|
|||
my $n_steps = 1; | |||
while ( $ref eq 'REF' ) { | |||
$data = $$data; | |||
$ref | |||
= !Scalar::Util::blessed($data) ? ref $data | |||
: !$data->can('does') ? '' | |||
: $data->does('Elastic::Model::Role::Doc') ? 'ElasticDoc' | |||
: $data->isa('Elastic::Model::UID') ? 'ElasticUID' | |||
: ''; | |||
$n_steps++ if $ref; | |||
} | |||
|
|||
return ( $ref || 'SCALAR' ) . ( 'REF' x $n_steps ); | |||
} | |||
|
|||
1; | |||
|
|||
# ABSTRACT: An Elastic::Model specific subclass of L<ElasticSearch::SearchBuilder> | |||
|
|||
=head1 DESCRIPTION | |||
L<Elastic::Model::SearchBuilder> is a sub-class of L<ElasticSearch::SearchBuilder> | |||
to add automatic handling of L<Elastic::Doc> and L<Elastic::Model::UID> | |||
values. | |||
This document just explains the functionality that | |||
L<Elastic::Model::SearchBuilder> adds. | |||
B<For the full SearchBuilder docs, see L<ElasticSearch::SearchBuilder>>. | |||
=head1 THE PROBLEM | |||
Consider this class (where C<MyApp::User> is also an | |||
L<Elastic::Doc> class): | |||
package MyApp::Comment; | |||
use Elastic::Doc; | |||
has 'user' => ( | |||
is => 'ro', | |||
isa => 'MyApp::User, | |||
); | |||
has 'text' => ( | |||
is => 'ro', | |||
isa => 'Str', | |||
); | |||
We can create a comment as follows: | |||
$domain->create( | |||
comment => { | |||
text => 'I like Elastic::Model', | |||
user => $user, | |||
} | |||
); | |||
The C<comment> object would be stored in ElasticSearch as something like this: | |||
{ | |||
text => "I like Elastic::Model", | |||
user => { | |||
uid => { | |||
index => 'myapp', | |||
type => 'user', | |||
id => 'abcdefg', | |||
}, | |||
.... any other user fields.... | |||
} | |||
} | |||
In order to search for any comments by user C<$user>, you would need to do this: | |||
$view->type('comment') | |||
->filterb( | |||
'user.uid.index' => $user->uid->index, | |||
'user.uid.type' => $user->uid->type, | |||
'user.uid.id' => $user->uid->id, | |||
) | |||
->search; | |||
=head1 THE SOLUTION | |||
With L<Elastic::Model::SearchBuilder>, you can do it as follows: | |||
$view->type('comment') | |||
->filterb( user => $user ) | |||
->search; | |||
Or with the C<UID>: | |||
$view->type('comment') | |||
->filterb( user => $user->uid ) | |||
->search; | |||
=head1 FURTHER EXAMPLES | |||
=head2 Query or Filter | |||
This works for both queries and filters, eg: | |||
$view->queryb ( user => $user )->search; | |||
$view->filterb( user => $user )->search; | |||
=head2 Doc or UID | |||
You can use either the doc/object itself, or an L<Elastic::Model::UID> object: | |||
$uid = $user->uid; | |||
$view->queryb ( user => $uid )->search; | |||
$view->filterb( user => $uid )->search; | |||
=head2 Negating queries: | |||
$view->queryb ( user => { '!=' => $user })->search; | |||
$view->filterb( user => { '!=' => $user })->search; | |||
=head2 "IN" queries | |||
$view->queryb ( user => \@users )->search; | |||
$view->filterb( user => \@users )->search; | |||
=head2 "NOT IN" queries | |||
$view->queryb ( user => { '!=' => \@users })->search; | |||
$view->filterb( user => { '!=' => \@users })->search; | |||
=head2 Docs that contain the C<$user> in any field | |||
$view->queryb ( $user )->search; | |||
$view->filterb( $user )->search; | |||
=head2 Docs where C<status> is C<active> and any field contains C<$user>: | |||
$view->queryb ( '' => $user, status => 'active' )->search | |||
$view->filterb( '' => $user, status => 'active' )->search | |||
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Original file line | Diff line number | Diff line change |
---|---|---|---|
@@ -0,0 +1,37 @@ | |||
#!perl | |||
|
|||
use strict; | |||
use warnings; | |||
|
|||
use Test::More; | |||
use Test::Differences; | |||
use Test::Exception; | |||
|
|||
BEGIN { | |||
use_ok('Elastic::Model::SearchBuilder') || print "Bail out!"; | |||
} | |||
|
|||
diag ""; | |||
diag( | |||
"Testing Elastic::Model::SearchBuilder $Elastic::Model::SearchBuilder::VERSION, Perl $], $^X" | |||
); | |||
|
|||
my $a; | |||
ok $a = Elastic::Model::SearchBuilder->new, 'Instantiate'; | |||
ok $a = $a->new, 'Instantiate from ref'; | |||
|
|||
is( scalar $a->filter(), undef, 'Empty ()' ); | |||
is( scalar $a->filter(undef), undef, '(undef)' ); | |||
is( scalar $a->filter( [] ), undef, 'Empty []' ); | |||
is( scalar $a->filter( {} ), undef, 'Empty {}' ); | |||
is( scalar $a->filter( [ [], {} ] ), undef, 'Empty [[]{}]' ); | |||
is( scalar $a->filter( { [], {} } ), undef, 'Empty {[]{}}' ); | |||
is( scalar $a->filter( { -ids => [] } ), undef, 'IDS=>[]' ); | |||
eq_or_diff $a->filter( \{ term => { foo => 1 } } ), | |||
{ filter => { term => { foo => 1 } } }, | |||
'\\{}'; | |||
|
|||
throws_ok { $a->filter( 1, 2 ) } qr/Too many params/, '1,2'; | |||
throws_ok { $a->filter( [undef] ) } qr/UNDEF in arrayref/, '[undef]'; | |||
|
|||
done_testing; |
Oops, something went wrong.