File Coverage

blib/lib/SQL/Composer/Select.pm
Criterion Covered Total %
statement 162 163 99.3
branch 68 78 87.1
condition 12 17 70.5
subroutine 17 17 100.0
pod 0 5 0.0
total 259 280 92.5


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