Skip to content

Commit a1e2cf7

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 a1e2cf7

File tree

7 files changed

+7
-261
lines changed

7 files changed

+7
-261
lines changed

docs/API-docs.md

Lines changed: 0 additions & 30 deletions
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

Lines changed: 0 additions & 141 deletions
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

Lines changed: 0 additions & 15 deletions
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

Lines changed: 0 additions & 2 deletions
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

Lines changed: 0 additions & 17 deletions
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

Lines changed: 0 additions & 9 deletions
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

Lines changed: 7 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -82,69 +82,29 @@ test_psgi app, sub {
8282
my $json = decode_json_ok($res);
8383
is( @{ $json->{hits}->{hits} }, 0, '0 results' );
8484

85-
ok( $res = $cb->( GET '/author/DOY?join=release' ),
86-
'GET /author/DOY?join=release' );
87-
88-
$json = decode_json_ok($res);
89-
is( @{ $json->{release}->{hits}->{hits} }, 4, 'joined 4 releases' );
90-
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-
10985
ok(
11086
$res = $cb->(
111-
POST '/author/_search?join=release',
87+
POST '/author/_search',
11288
Content => encode_json( {
11389
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-
]
90+
bool => {
91+
must => [
92+
{
93+
term => { 'pauseid' => 'DOY' }
12794
}
128-
}
95+
]
12996
}
13097
}
13198
} )
13299
),
133-
'POST /author/_search?join=release with query body'
100+
'POST /author/_search with query body'
134101
);
135102

136103
my $doy = $json;
137104
$json = decode_json_ok($res);
138105

139106
is( @{ $json->{hits}->{hits} }, 1, '1 hit' );
140107

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-
148108
my $links = delete $doy->{links};
149109
is_deeply(
150110
[ sort keys %{$links} ],

0 commit comments

Comments
 (0)