Skip to content

Commit ab05bf2

Browse files
committed
remove join parameter
The join feature is something we try to add on top of Elasticsearch. It complicates our interactions with external queries and we don't use them anywhere in our own code.
1 parent b7bbdb4 commit ab05bf2

File tree

7 files changed

+5
-282
lines changed

7 files changed

+5
-282
lines changed

docs/API-docs.md

-30
Original file line numberDiff line numberDiff line change
@@ -51,36 +51,6 @@ Performing a search without any constraints is an easy way to get sample data
5151
* [`/rating/_search`](https://fastapi.metacpan.org/v1/rating/_search)
5252
* [`/release/_search`](https://fastapi.metacpan.org/v1/release/_search)
5353

54-
## Joins
55-
56-
ElasticSearch itself doesn't support joining data across multiple types. The API server can, however, handle a `join` query parameter if the underlying type was set up accordingly. Browse https://github.com/metacpan/metacpan-api/blob/master/lib/MetaCPAN/Server/Controller/ to see all join conditions. Here are some examples.
57-
58-
Joins on documents:
59-
60-
* [/author/PERLER?join=favorite](https://fastapi.metacpan.org/v1/author/PERLER?join=favorite)
61-
* [/author/PERLER?join=favorite&join=release](https://fastapi.metacpan.org/v1/author/PERLER?join=favorite&join=release)
62-
* [/release/Moose?join=author](https://fastapi.metacpan.org/v1/release/Moose?join=author)
63-
* [/module/Moose?join=release](https://fastapi.metacpan.org/v1/module/Moose?join=release)
64-
65-
Joins on search results is work in progress.
66-
67-
Restricting the joined results can be done by using the [boolean "should"](https://www.elastic.co/guide/en/elasticsearch/reference/2.4/query-dsl-bool-query.html) occurrence type:
68-
69-
```sh
70-
curl -XPOST https://fastapi.metacpan.org/v1/author/PERLER?join=release -d '
71-
{
72-
"query": {
73-
"bool": {
74-
"should": [{
75-
"term": {
76-
"release.status": "latest"
77-
}
78-
}]
79-
}
80-
}
81-
}'
82-
```
83-
8454
## JSONP
8555

8656
Simply add a `callback` query parameter with the name of your callback, and you'll get a JSONP response.

lib/MetaCPAN/Server/Controller.pm

-141
Original file line numberDiff line numberDiff line change
@@ -26,14 +26,6 @@ has type => (
2626
default => sub { shift->action_namespace },
2727
);
2828

29-
has relationships => (
30-
is => 'ro',
31-
isa => HashRef,
32-
default => sub { {} },
33-
traits => ['Hash'],
34-
handles => { has_relationships => 'count' },
35-
);
36-
3729
my $MAX_SIZE = 5000;
3830

3931
# apply "filters" like \&model but for fabricated data
@@ -128,90 +120,6 @@ sub search : Path('_search') : ActionClass('~Deserialize') {
128120
} or do { $self->internal_error( $c, $@ ) };
129121
}
130122

131-
sub join : ActionClass('~Deserialize') {
132-
my ( $self, $c ) = @_;
133-
my $joins = $self->relationships;
134-
my @req_joins = $c->req->param('join');
135-
my $is_get = ref $c->stash->{hits} ? 0 : 1;
136-
my $query
137-
= $c->req->params->{q}
138-
? { query => { query_string => { query => $c->req->params->{q} } } }
139-
: $c->req->data ? $c->req->data
140-
: { query => { match_all => {} } };
141-
$c->detach(
142-
'/not_allowed',
143-
[
144-
'unknown join type, valid values are '
145-
. Moose::Util::english_list( keys %$joins )
146-
]
147-
) if ( scalar grep { !$joins->{$_} } @req_joins );
148-
149-
while ( my ( $join, $config ) = each %$joins ) {
150-
my $has_many = ref $config->{type};
151-
my ($type) = $has_many ? @{ $config->{type} } : $config->{type};
152-
my $cself = $config->{self} || $join;
153-
next unless ( grep { $_ eq $join } @req_joins );
154-
my $data
155-
= $is_get
156-
? [ $c->stash ]
157-
: [
158-
map {
159-
$_->{_source}
160-
|| single_valued_arrayref_to_scalar( $_->{fields} )
161-
} @{ $c->stash->{hits}->{hits} }
162-
];
163-
my @ids = List::AllUtils::uniq grep {defined}
164-
map { ref $cself eq 'CODE' ? $cself->($_) : $_->{$cself} } @$data;
165-
my $filter = { terms => { $config->{foreign} => [@ids] } };
166-
my $filtered = {%$query}; # don't work on $query
167-
$filtered->{filter}
168-
= $query->{filter}
169-
? { and => [ $filter, $query->{filter} ] }
170-
: $filter;
171-
my $foreign = eval {
172-
$c->model("CPAN::$type")->query( $filtered->{query} )
173-
->filter( $filtered->{filter} )->size(1000)->raw->all;
174-
} or do { $self->internal_error( $c, $@ ) };
175-
$c->detach(
176-
"/not_allowed",
177-
[
178-
'The number of joined documents exceeded the allowed number of 1000 documents by '
179-
. ( $foreign->{hits}->{total} - 1000 )
180-
. '. Please reduce the number of documents or apply additional filters.'
181-
]
182-
) if ( $foreign->{hits}->{total} > 1000 );
183-
$c->stash->{took} += $foreign->{took} unless ($is_get);
184-
185-
if ($has_many) {
186-
my $many;
187-
for ( @{ $foreign->{hits}->{hits} } ) {
188-
my $list = $many->{ $_->{_source}->{ $config->{foreign} } }
189-
||= [];
190-
push( @$list, $_ );
191-
}
192-
$foreign = $many;
193-
}
194-
else {
195-
$foreign = { map { $_->{_source}->{ $config->{foreign} } => $_ }
196-
@{ $foreign->{hits}->{hits} } };
197-
}
198-
for (@$data) {
199-
my $key = ref $cself eq 'CODE' ? $cself->($_) : $_->{$cself};
200-
next unless ($key);
201-
my $result = $foreign->{$key};
202-
$_->{$join}
203-
= $has_many
204-
? {
205-
hits => {
206-
hits => $result,
207-
total => scalar @{ $result || [] }
208-
}
209-
}
210-
: $result;
211-
}
212-
}
213-
}
214-
215123
sub not_found : Private {
216124
my ( $self, $c ) = @_;
217125
$c->cdn_never_cache(1);
@@ -238,57 +146,8 @@ sub internal_error {
238146

239147
sub end : Private {
240148
my ( $self, $c ) = @_;
241-
$c->forward('join')
242-
if ( $self->has_relationships && $c->req->param('join') );
243149
$c->forward('/end');
244150
}
245151

246152
__PACKAGE__->meta->make_immutable;
247153
1;
248-
249-
__END__
250-
251-
=head1 ATTRIBUTES
252-
253-
=head2 relationships
254-
255-
MetaCPAN::Server::Controller::Author->config(
256-
relationships => {
257-
release => {
258-
type => ['Release'],
259-
self => 'pauseid',
260-
foreign => 'author',
261-
}
262-
}
263-
);
264-
265-
Contains a HashRef of relationships with other controllers.
266-
If C<type> is an ArrayRef, the relationship is considered a
267-
I<has many> relationship.
268-
269-
Unless a C<self> exists, the name of the relationship is used
270-
as key to join on. C<self> can also be a CodeRef, if the foreign
271-
key is build from several local keys. In this case, again the name of
272-
the relationship is used as key in the result.
273-
274-
C<foreign> refers to the foreign key on the C<type> controller the data
275-
is joined with.
276-
277-
=head1 ACTIONS
278-
279-
=head2 join
280-
281-
This action is called if the controller has L</relationships> defined
282-
and if one or more C<join> query parameters are defined. It then
283-
does a I<left join> based on the information provided by
284-
L</relationships>.
285-
286-
This works both for GET requests, where only one document is requested
287-
and search requests, where a number of documents is returned.
288-
It also passes through search data (either the C<q> query string or
289-
the request body).
290-
291-
B<The number of documents that can be joined is limited to 1000 per
292-
relationship for now.>
293-
294-
=cut

lib/MetaCPAN/Server/Controller/Author.pm

-15
Original file line numberDiff line numberDiff line change
@@ -10,21 +10,6 @@ BEGIN { extends 'MetaCPAN::Server::Controller' }
1010

1111
with 'MetaCPAN::Server::Role::JSONP';
1212

13-
__PACKAGE__->config(
14-
relationships => {
15-
release => {
16-
type => ['Release'],
17-
self => 'pauseid',
18-
foreign => 'author',
19-
},
20-
favorite => {
21-
type => ['Favorite'],
22-
self => 'user',
23-
foreign => 'user',
24-
}
25-
}
26-
);
27-
2813
# https://fastapi.metacpan.org/v1/author/LLAP
2914
sub get : Path('') : Args(1) {
3015
my ( $self, $c, $id ) = @_;

lib/MetaCPAN/Server/Controller/Changes.pm

-2
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,6 @@ BEGIN { extends 'MetaCPAN::Server::Controller' }
1212

1313
with 'MetaCPAN::Server::Role::JSONP';
1414

15-
# TODO: __PACKAGE__->config(relationships => ?)
16-
1715
has '+type' => ( default => 'file' );
1816

1917
sub index : Chained('/') : PathPart('changes') : CaptureArgs(0) {

lib/MetaCPAN/Server/Controller/File.pm

-17
Original file line numberDiff line numberDiff line change
@@ -11,23 +11,6 @@ BEGIN { extends 'MetaCPAN::Server::Controller' }
1111

1212
with 'MetaCPAN::Server::Role::JSONP';
1313

14-
__PACKAGE__->config(
15-
relationships => {
16-
author => {
17-
type => 'Author',
18-
foreign => 'pauseid',
19-
},
20-
release => {
21-
type => 'Release',
22-
self => sub {
23-
ElasticSearchX::Model::Util::digest( $_[0]->{author},
24-
$_[0]->{release} );
25-
},
26-
foreign => 'id',
27-
}
28-
}
29-
);
30-
3114
sub find : Path('') {
3215
my ( $self, $c, $author, $release, @path ) = @_;
3316

lib/MetaCPAN/Server/Controller/Release.pm

-9
Original file line numberDiff line numberDiff line change
@@ -10,15 +10,6 @@ BEGIN { extends 'MetaCPAN::Server::Controller' }
1010

1111
with 'MetaCPAN::Server::Role::JSONP';
1212

13-
__PACKAGE__->config(
14-
relationships => {
15-
author => {
16-
type => 'Author',
17-
foreign => 'pauseid',
18-
}
19-
}
20-
);
21-
2213
sub find : Path('') : Args(1) {
2314
my ( $self, $c, $name ) = @_;
2415
my $file = $self->model($c)->find($name);

t/server/controller/author.t

+5-68
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ my %tests = (
4040
test_psgi app, sub {
4141
my $cb = shift;
4242
while ( my ( $k, $v ) = each %tests ) {
43-
ok( my $res = $cb->( GET $k), "GET $k" );
43+
ok( my $res = $cb->( GET $k ), "GET $k" );
4444
is( $res->code, $v->{code}, "code " . $v->{code} );
4545
is(
4646
$res->header('content-type'),
@@ -79,73 +79,13 @@ test_psgi app, sub {
7979
'POST _search'
8080
);
8181

82-
my $json = decode_json_ok($res);
83-
is( @{ $json->{hits}->{hits} }, 0, '0 results' );
82+
ok( $res = $cb->( GET '/author/DOY' ), 'GET /author/DOY' );
8483

85-
ok( $res = $cb->( GET '/author/DOY?join=release' ),
86-
'GET /author/DOY?join=release' );
84+
my $doy = decode_json_ok($res);
8785

88-
$json = decode_json_ok($res);
89-
is( @{ $json->{release}->{hits}->{hits} }, 4, 'joined 4 releases' );
86+
is( $doy->{pauseid}, 'DOY', 'found author' );
9087

91-
ok(
92-
$res = $cb->(
93-
POST '/author/DOY?join=release',
94-
Content => encode_json( {
95-
query => {
96-
constant_score =>
97-
{ filter => { term => { status => 'latest' } } }
98-
}
99-
} )
100-
),
101-
'POST /author/DOY?join=release with query body',
102-
);
103-
104-
$json = decode_json_ok($res);
105-
is( @{ $json->{release}->{hits}->{hits} }, 1, 'joined 1 release' );
106-
is( $json->{release}->{hits}->{hits}->[0]->{_source}->{status},
107-
'latest', '1 release has status latest' );
108-
109-
ok(
110-
$res = $cb->(
111-
POST '/author/_search?join=release',
112-
Content => encode_json( {
113-
query => {
114-
constant_score => {
115-
filter => {
116-
bool => {
117-
should => [
118-
{
119-
term => {
120-
'status' => 'latest'
121-
}
122-
},
123-
{
124-
term => { 'pauseid' => 'DOY' }
125-
}
126-
]
127-
}
128-
}
129-
}
130-
}
131-
} )
132-
),
133-
'POST /author/_search?join=release with query body'
134-
);
135-
136-
my $doy = $json;
137-
$json = decode_json_ok($res);
138-
139-
is( @{ $json->{hits}->{hits} }, 1, '1 hit' );
140-
141-
my $release_count = delete $doy->{release_count};
142-
is_deeply(
143-
[ sort keys %{$release_count} ],
144-
[qw< backpan-only cpan latest >],
145-
'release_count has the correct keys'
146-
);
147-
148-
my $links = delete $doy->{links};
88+
my $links = $doy->{links};
14989
is_deeply(
15090
[ sort keys %{$links} ],
15191
[
@@ -154,9 +94,6 @@ test_psgi app, sub {
15494
'links has the correct keys'
15595
);
15696

157-
my $source = $json->{hits}->{hits}->[0]->{_source};
158-
is_deeply( $doy, $source, 'same result as direct get' );
159-
16097
{
16198
ok( my $res = $cb->( GET '/author/_search?q=*&size=99999' ),
16299
'GET size=99999' );

0 commit comments

Comments
 (0)