File Coverage

blib/lib/SQL/Composer/Select.pm
Criterion Covered Total %
statement 161 162 99.3
branch 66 76 86.8
condition 14 19 73.6
subroutine 17 17 100.0
pod 0 5 0.0
total 258 279 92.4


line stmt bran cond sub pod time code
1             package SQL::Composer::Select;
2              
3 3     3   12886 use strict;
  3         4  
  3         67  
4 3     3   8 use warnings;
  3         4  
  3         64  
5              
6             require Carp;
7 3     3   10 use Scalar::Util ();
  3         3  
  3         48  
8 3     3   877 use SQL::Composer::Join;
  3         5  
  3         66  
9 3     3   9 use SQL::Composer::Expression;
  3         3  
  3         39  
10 3     3   9 use SQL::Composer::Quoter;
  3         3  
  3         3216  
11              
12             sub new {
13 31     31 0 28042 my $class = shift;
14 31         81 my (%params) = @_;
15              
16 31         56 my $self = { table => $params{from} };
17 31         50 bless $self, $class;
18              
19 31         38 $self->{from} = $params{from};
20 31         30 $self->{columns} = $params{columns};
21              
22 31         28 $self->{join} = $params{join};
23             $self->{join} = [$self->{join}]
24 31 100 100     88 if $self->{join} && ref $self->{join} ne 'ARRAY';
25              
26             $self->{quoter} =
27 31   33     159 $params{quoter} || SQL::Composer::Quoter->new(driver => $params{driver});
28              
29 31         40 my $sql = '';
30 31         28 my @bind;
31              
32             my @columns =
33 52         78 map { $self->_prepare_column($_, $self->{from}, \@bind) }
34 31         23 @{$self->{columns}};
  31         51  
35 31         57 push @columns, $self->_collect_columns_from_joins($self->{join});
36              
37 31         31 $sql .= 'SELECT ';
38              
39 31 50       48 if (@columns) {
40 31         67 $sql .= join ',', @columns;
41             }
42              
43 31         31 $sql .= ' FROM ';
44 31         40 $sql .= $self->_quote($params{from});
45              
46 31 100       49 if (my $joins = $self->{join}) {
47 7         12 my ($join_sql, $join_bind) = $self->_build_join($joins);
48 7         10 $sql .= $join_sql;
49 7         9 push @bind, @$join_bind;
50             }
51              
52 31 100       45 if (my $where = $params{where}) {
53 1 50       13 if (!Scalar::Util::blessed($where)) {
54             $where = SQL::Composer::Expression->new(
55             default_prefix => $self->{from},
56             quoter => $self->{quoter},
57 1         8 expr => $where
58             );
59             }
60              
61 1 50       3 if (my $where_sql = $where->to_sql) {
62 1         2 $sql .= ' WHERE ' . $where_sql;
63 1         3 push @bind, $where->to_bind;
64             }
65             }
66              
67 31 100       41 if (my $group_bys = $params{group_by}) {
68 4 100       10 $group_bys = [$group_bys] unless ref $group_bys eq 'ARRAY';
69              
70 4         3 my @group_by;
71 4         4 foreach my $group_by (@$group_bys) {
72             push @group_by,
73             ref($group_by)
74             ? $$group_by
75 6 100       13 : $self->_quote($group_by, $self->{from});
76             }
77              
78 4         8 $sql .= ' GROUP BY ' . join(', ', @group_by);
79             }
80              
81 31 100       47 if (my $having = $params{having}) {
82 1 50       5 if (!Scalar::Util::blessed($having)) {
83             $having = SQL::Composer::Expression->new(
84             default_prefix => $self->{from},
85             quoter => $self->{quoter},
86 1         3 expr => $having
87             );
88             }
89              
90 1 50       2 if (my $having_sql = $having->to_sql) {
91 1         3 $sql .= ' HAVING ' . $having->to_sql;
92 1         2 push @bind, $having->to_bind;
93             }
94             }
95              
96 31 100       58 if (my $order_by = $params{order_by}) {
97 8         10 $sql .= ' ORDER BY ';
98 8 100       13 if (ref $order_by) {
99 7 100       13 if (ref($order_by) eq 'ARRAY') {
    50          
100 6         4 my @order;
101 6         19 while (my ($key, $value) = splice @$order_by, 0, 2) {
102 7         3 my $order_type;
103              
104 7 100       10 if (ref $value) {
105 1         2 $order_type = ' ' . $$value;
106             }
107             else {
108 6   100     14 $order_type = uc($value // '');
109 6 100 100     16 if ($order_type eq 'ASC' || $order_type eq 'DESC') {
110 4         5 $order_type = " $order_type";
111             }
112             else {
113 2         3 $order_type = '';
114             }
115             }
116              
117 7 100       10 if (ref($key) eq 'SCALAR') {
118 1         4 push @order, $$key . $order_type;
119             }
120             else {
121             push @order,
122 6         8 $self->_quote($key, $self->{from}) . $order_type;
123             }
124             }
125 6         11 $sql .= join ',', @order;
126             }
127             elsif (ref($order_by) eq 'SCALAR') {
128 1         1 $sql .= $$order_by;
129             }
130             else {
131 0         0 Carp::croak('unexpected reference');
132             }
133             }
134             else {
135 1         2 $sql .= $self->_quote($order_by);
136             }
137             }
138              
139 31 100       43 if (defined(my $limit = $params{limit})) {
140 4         8 $sql .= ' LIMIT ' . $limit;
141             }
142              
143 31 100       47 if (defined(my $offset = $params{offset})) {
144 2         3 $sql .= ' OFFSET ' . $offset;
145             }
146              
147 31 100       38 if ($params{for_update}) {
148 1         2 $sql .= ' FOR UPDATE';
149             }
150              
151 31         36 $self->{sql} = $sql;
152 31         38 $self->{bind} = \@bind;
153              
154 31         80 return $self;
155             }
156              
157 1     1 0 3 sub table { shift->{table} }
158              
159 30     30 0 102 sub to_sql { shift->{sql} }
160 30 50   30 0 7624 sub to_bind { @{shift->{bind} || []} }
  30         119  
161              
162             sub from_rows {
163 10     10 0 3175 my $self = shift;
164 10         12 my ($rows) = @_;
165              
166 10         11 my $result = [];
167 10         16 foreach my $row (@$rows) {
168 10         8 my $set = {};
169              
170 10         18 $self->_populate($set, $row, $self->{columns});
171              
172 10         20 $self->_populate_joins($set, $row, $self->{join});
173              
174 10         13 push @$result, $set;
175             }
176              
177 10         50 return $result;
178             }
179              
180             sub _prepare_column {
181 61     61   47 my $self = shift;
182 61         54 my ($column, $prefix, $bind) = @_;
183              
184 61 100       113 if (ref $column eq 'SCALAR') {
    100          
185 1         3 return $$column;
186             }
187             elsif (ref $column eq 'HASH') {
188             return (
189             ref($column->{-col})
190             ? (
191             do {
192 2         3 my $value = $column->{-col};
193 2 100       5 if (ref $$value eq 'ARRAY') {
194 1         2 my $sql = $$value->[0];
195 1         3 push @$bind, @$$value[1 .. $#{$$value}];
  1         2  
196 1         4 $sql;
197             }
198             else {
199 1         3 $$value;
200             }
201             }
202             )
203             : $self->_quote($column->{-col}, $prefix)
204             )
205             . ' AS '
206 3 100       9 . $self->_quote($column->{-as});
207             }
208             else {
209 57         78 return $self->_quote($column, $prefix);
210             }
211             }
212              
213             sub _populate {
214 19     19   15 my $self = shift;
215 19         17 my ($set, $row, $columns) = @_;
216              
217 19         11 my $name;
218 19         18 foreach my $column (@$columns) {
219 20 100       37 if (ref($column) eq 'HASH') {
    100          
220 2         4 $name = $column->{-as};
221             }
222             elsif (ref($column) eq 'SCALAR') {
223 1         2 $name = $$column;
224             }
225             else {
226 17         14 $name = $column;
227             }
228              
229 20         41 $set->{$name} = shift @$row;
230             }
231             }
232              
233             sub _populate_joins {
234 13     13   11 my $self = shift;
235 13         13 my ($set, $row, $joins) = @_;
236              
237 13         14 foreach my $join (@$joins) {
238 9   66     29 my $join_source = $join->{rel_name} || $join->{as} || $join->{source};
239              
240 9   50     33 $set->{$join_source} ||= {};
241 9         12 $self->_populate($set->{$join_source}, $row, $join->{columns});
242              
243 9 100       22 if (my $subjoins = $join->{join}) {
244 3 50       6 $subjoins = [$subjoins] unless ref $subjoins eq 'ARRAY';
245              
246 3         7 $self->_populate_joins($set->{$join_source}, $row, $subjoins);
247             }
248             }
249             }
250              
251             sub _collect_columns_from_joins {
252 34     34   27 my $self = shift;
253 34         27 my ($joins) = @_;
254              
255 34 100 66     86 return () unless $joins && @$joins;
256              
257 10         11 my @join_columns;
258 10         12 foreach my $join_params (@$joins) {
259 11 100       15 if (my $join_columns = $join_params->{columns}) {
260             push @join_columns, map {
261 9         8 $self->_prepare_column($_,
262             $join_params->{as}
263             ? $join_params->{as}
264             : $join_params->{source})
265 9 100       18 } @$join_columns;
266             }
267              
268 11 100       22 if (my $subjoins = $join_params->{join}) {
269 3 50       7 $subjoins = [$subjoins] unless ref $subjoins eq 'ARRAY';
270              
271 3         6 push @join_columns, $self->_collect_columns_from_joins($subjoins);
272             }
273             }
274              
275 10         12 return @join_columns;
276             }
277              
278             sub _build_join {
279 10     10   7 my $self = shift;
280 10         10 my ($joins) = @_;
281              
282 10 50       18 $joins = [$joins] unless ref $joins eq 'ARRAY';
283              
284 10         8 my $sql = '';
285 10         8 my @bind;
286 10         10 foreach my $join_params (@$joins) {
287             my $join =
288 11         42 SQL::Composer::Join->new(quoter => $self->{quoter}, %$join_params);
289              
290 11         21 $sql .= ' ' . $join->to_sql;
291 11         18 push @bind, $join->to_bind;
292              
293 11 100       31 if (my $subjoin = $join_params->{join}) {
294 3         7 my ($subsql, $subbind) = $self->_build_join($subjoin);
295 3         3 $sql .= $subsql;
296 3         7 push @bind, @$subbind;
297             }
298             }
299              
300 10         16 return ($sql, \@bind);
301             }
302              
303             sub _quote {
304 103     103   69 my $self = shift;
305 103         81 my ($column, $prefix) = @_;
306              
307 103         165 return $self->{quoter}->quote($column, $prefix);
308             }
309              
310             1;
311             __END__