File Coverage

blib/lib/Aniki/Plugin/SelectJoined.pm
Criterion Covered Total %
statement 66 66 100.0
branch 10 16 62.5
condition 3 5 60.0
subroutine 10 10 100.0
pod 0 2 0.0
total 89 99 89.9


line stmt bran cond sub pod time code
1             package Aniki::Plugin::SelectJoined;
2 1     1   628 use 5.014002;
  1         4  
3              
4 1     1   6 use namespace::autoclean;
  1         2  
  1         8  
5 1     1   403 use Mouse::Role;
  1         902  
  1         4  
6 1     1   306 use Aniki::QueryBuilder;
  1         2  
  1         23  
7 1     1   327 use Aniki::Result::Collection::Joined;
  1         88  
  1         35  
8 1     1   7 use Carp qw/croak/;
  1         2  
  1         676  
9              
10             requires qw/schema query_builder suppress_row_objects txn_manager execute/;
11              
12             Aniki::QueryBuilder->load_plugin('JoinSelect');
13              
14             sub select_joined {
15 3     3 0 5622 my ($self, $base_table, $join_conditions, $where, $opt) = @_;
16 3 50       11 croak '(Aniki::Plugin::SelectJoined#select_joined) `where` condition must be a reference.' unless ref $where;
17              
18 3         7 my @table_names = ($base_table);
19 3         12 for (my $i = 0; my $table = $join_conditions->[$i]; $i += 2) {
20 3         11 push @table_names => $table;
21             }
22 3         7 my @tables = map { $self->schema->get_table($_) } @table_names;
  6         18  
23              
24 3         13 my $name_sep = $self->query_builder->name_sep;
25 3         23 my @columns;
26 3         8 for my $table (@tables) {
27 6         23 my $table_name = $table->name;
28             push @columns =>
29 24         63 map { "$table_name$name_sep$_" }
30 6         22 map { $_->name } $table->get_fields();
  24         63  
31             }
32              
33 3         8 my ($sql, @bind) = $self->query_builder->join_select($base_table, $join_conditions, \@columns, $where, $opt);
34 3         2566 return $self->select_joined_by_sql($sql, \@bind, {
35             table_names => \@table_names,
36             columns => \@columns,
37             %$opt,
38             });
39             }
40              
41             sub select_joined_by_sql {
42 3     3 0 10 my ($self, $sql, $bind, $opt) = @_;
43 3   50     10 $opt //= {};
44              
45 3 50       10 my $table_names = $opt->{table_names} or croak 'table_names is required';
46 3 50       9 my $columns = $opt->{columns} or croak 'columns is required';
47 3 100       12 my $prefetch = exists $opt->{prefetch} ? $opt->{prefetch} : {};
48              
49 3   66     19 my $prefetch_enabled_fg = %$prefetch && !$self->suppress_row_objects;
50 3 100       8 if ($prefetch_enabled_fg) {
51 1 50       2 my $txn; $txn = $self->txn_scope unless $self->txn_manager->in_transaction;
  1         9  
52              
53 1         100 my $sth = $self->execute($sql, @$bind);
54 1         6 my $result = $self->_fetch_joined_by_sth($sth, $table_names, $columns);
55              
56 1         3 for my $table_name (@$table_names) {
57 2         8 my $rows = $result->rows($table_name);
58 2         7 my $prefetch = $prefetch->{$table_name};
59 2 50       10 $prefetch = [$prefetch] if ref $prefetch eq 'HASH';
60 2         13 $self->fetch_and_attach_relay_data($table_name, $prefetch, $rows);
61             }
62              
63 1 50       8 $txn->rollback if defined $txn; ## for read only
64 1         51 return $result;
65             }
66             else {
67 2         13 my $sth = $self->execute($sql, @$bind);
68 2         21 return $self->_fetch_joined_by_sth($sth, $table_names, $columns);
69             }
70             }
71              
72             sub _fetch_joined_by_sth {
73 3     3   10 my ($self, $sth, $table_names, $columns) = @_;
74 3         8 my @rows;
75              
76             my %row;
77 3         42 $sth->bind_columns(\@row{@$columns});
78 3         187 push @rows => $self->_seperate_rows(\%row) while $sth->fetch;
79 3         12 $sth->finish;
80              
81 3         53 return Aniki::Result::Collection::Joined->new(
82             table_names => $table_names,
83             handler => $self,
84             row_datas => \@rows,
85             );
86             }
87              
88             sub _seperate_rows {
89 15     15   95 my ($self, $row) = @_;
90              
91 15         48 my $name_sep = quotemeta $self->query_builder->name_sep;
92              
93 15         111 my %rows;
94 15         54 for my $full_named_column (keys %$row) {
95 120         332 my ($table_name, $column) = split /$name_sep/, $full_named_column, 2;
96 120         293 $rows{$table_name}{$column} = $row->{$full_named_column};
97             }
98              
99 15         161 return \%rows;
100             }
101              
102             1;
103             __END__
104              
105             =pod
106              
107             =encoding utf-8
108              
109             =head1 NAME
110              
111             Aniki::Plugin::SelectJoined - Support for Joined query
112              
113             =head1 SYNOPSIS
114              
115             package MyDB;
116             use Mouse v2.4.5;
117             extends qw/Aniki/;
118             with qw/Aniki::Plugin::SelectJoined/;
119              
120             package main;
121             my $db = MyDB->new(...);
122              
123             my $result = $db->select_joined(user_item => [
124             user => {'user_item.user_id' => 'user.id'},
125             item => {'user_item.item_id' => 'item.id'},
126             ], {
127             'user.id' => 2,
128             }, {
129             order_by => 'user_item.item_id',
130             });
131              
132             for my $row ($result->all) {
133             my $user_item = $row->user_item;
134             my $user = $row->user;
135             my $item = $row->item;
136              
137             ...
138             }
139              
140             =head1 SEE ALSO
141              
142             L<Teng::Plugin::SelectJoined>
143              
144             L<SQL::Maker::Plugin::JoinSelect>
145              
146             =head1 LICENSE
147              
148             Copyright (C) karupanerura.
149              
150             This library is free software; you can redistribute it and/or modify
151             it under the same terms as Perl itself.
152              
153             =head1 AUTHOR
154              
155             karupanerura E<lt>karupa@cpan.orgE<gt>
156              
157             =cut