File Coverage

blib/lib/EntityModel/Query.pm
Criterion Covered Total %
statement 151 203 74.3
branch 39 68 57.3
condition 0 4 0.0
subroutine 39 44 88.6
pod 24 24 100.0
total 253 343 73.7


line stmt bran cond sub pod time code
1             package EntityModel::Query;
2             {
3             $EntityModel::Query::VERSION = '0.102';
4             }
5             use EntityModel::Class {
6 16         450 _isa => [qw{EntityModel::Query::Base}],
7             'forUpdate' => 'string',
8             'limit' => 'int',
9             'offset' => 'int',
10             'field' => { type => 'array', subclass => 'EntityModel::Query::Field' },
11             'from' => { type => 'array', subclass => 'EntityModel::Query::FromTable' },
12             'join' => { type => 'array', subclass => 'EntityModel::Query::Join' },
13             'where' => { type => 'EntityModel::Query::Condition' },
14             'having' => { type => 'array', subclass => 'EntityModel::Query::Condition' },
15             'group' => { type => 'array', subclass => 'EntityModel::Query::GroupField' },
16             'order' => { type => 'array', subclass => 'EntityModel::Query::OrderField' },
17             'returning' => { type => 'array', subclass => 'EntityModel::Query::ReturningField' },
18             'db' => { type => 'EntityModel::DB', scope => 'private' },
19             'transaction' => { type => 'EntityModel::Transaction', scope => 'private' },
20 16     16   47285 };
  16         89214  
21 16     16   10815 no if $] >= 5.017011, warnings => "experimental::smartmatch";
  16         32  
  16         128  
22              
23             =head1 NAME
24              
25             EntityModel::Query - handle SQL queries
26              
27             =head1 VERSION
28              
29             version 0.102
30              
31             =head1 SYNOPSIS
32              
33             use EntityModel::Query;
34              
35             # Provide a definition on instantiation:
36             my $query = EntityModel::Query->new(
37             select => [qw(id name)]
38             from => 'table',
39             where => [ created => { '<' => '2010-01-01' } ],
40             limit => 5
41             );
42              
43             # or using chained methods:
44             my $query = EntityModel::Query->new
45             ->select( qw(id name) )
46             ->from( 'table' )
47             ->where(
48             created => { '<' => '2010-01-01' }
49             )
50             ->limit(5);
51              
52             # Extract query as SQL
53             my ($sql, @bind) = $query->sqlAndParameters;
54             my $sth = $dbh->prepare($sql);
55             $sth->execute(@bind);
56              
57             =head1 DESCRIPTION
58              
59             Provides an abstraction layer for building SQL queries programmatically.
60              
61             When generating the query, each of the components is called in turn to get an "inline SQL" arrayref. This is an arrayref consisting of
62             SQL string fragments interspersed with refs for items such as L names, direct scalar values, or L
63             names.
64              
65             As an example:
66              
67             [ 'select * from ', EntityModel::Entity->new('table'), ' where ', EntityModel::Field->new('something'), ' = ', \3 ]
68              
69             This can then be used by L to generate:
70              
71             'select * from "table" where "something" = ?', 3
72              
73             or as a plain SQL string (perhaps for diagnostic purposes) from L as:
74              
75             'select * from "table" where "something" = 3'
76              
77             =cut
78              
79             # Need both of these available and we may be running without EntityModel behind us
80 16     16   1163 use EntityModel::Entity;
  16         32  
  16         128  
81 16     16   533 use EntityModel::Field;
  16         42  
  16         124  
82              
83             # All the query subtypes
84 16     16   9776 use EntityModel::Query::Select;
  16         42  
  16         70  
85 16     16   9430 use EntityModel::Query::Update;
  16         42  
  16         68  
86 16     16   9396 use EntityModel::Query::Insert;
  16         75  
  16         73  
87 16     16   9525 use EntityModel::Query::Delete;
  16         42  
  16         593  
88              
89             # Components
90 16     16   97 use EntityModel::Query::FromTable;
  16         24  
  16         147  
91 16     16   82 use EntityModel::Query::Field;
  16         27  
  16         111  
92 16     16   466 use EntityModel::Query::InsertField;
  16         30  
  16         145  
93 16     16   9401 use EntityModel::Query::UpdateField;
  16         42  
  16         81  
94 16     16   523 use EntityModel::Query::OrderField;
  16         38  
  16         146  
95 16     16   87 use EntityModel::Query::Join;
  16         38  
  16         138  
96              
97             # PostgreSQL extension
98 16     16   381 use EntityModel::Query::ReturningField;
  16         26  
  16         137  
99              
100             # Multi-query pieces
101 16     16   9492 use EntityModel::Query::Union;
  16         47  
  16         75  
102 16     16   9650 use EntityModel::Query::UnionAll;
  16         45  
  16         71  
103 16     16   9274 use EntityModel::Query::Intersect;
  16         43  
  16         63  
104 16     16   9149 use EntityModel::Query::Except;
  16         40  
  16         131  
105              
106 16     16   642 use Carp qw/confess/;
  16         31  
  16         40580  
107              
108             =head1 METHODS
109              
110             =cut
111              
112             =head2 new
113              
114             Construct a new L. Most of the work is passed off to L.
115              
116             =cut
117              
118             sub new {
119 11     11 1 33238 my $class = shift;
120 11         42 my $self = bless { }, $class;
121 11 50       66 $self->parse_spec(@_) if @_;
122 11         71 return $self;
123             }
124              
125             =head2 type
126              
127             Returns the type of the current query. The query object will be reblessed into an appropriate
128             subclass depending on whether this is an insert, select, delete etc. A query that has not been
129             reblessed is invalid.
130              
131             =cut
132              
133 0     0 1 0 sub type { confess "Virtual type - this is likely because you did not specify valid insert/select/delete criteria"; }
134              
135             =head2 parse_spec
136              
137             Parse the specification we were given.
138              
139             =cut
140              
141             sub parse_spec {
142 11     11 1 24 my $self = shift;
143 11         39 my @details = @_;
144             SPEC:
145 11         37 while(@details) {
146 35         60 my $k = shift(@details);
147 35         52 my $v = shift(@details);
148              
149 35 50       78 next SPEC if $k eq 'key';
150              
151             # Queries such as 'insert into' => 'insert_into'
152 35         79 $k =~ s/\s+/_/g;
153              
154             # FIXME haxx
155 35 50       227 if($k eq 'db') {
    50          
    50          
156 0         0 $self->db($v);
157             } elsif($k eq 'transaction') {
158 0         0 $self->transaction($v);
159             } elsif(my $handler = $self->can_parse($k)) {
160 35         121 $handler->($self, $v);
161             } else {
162 0         0 die "Could not find method for $k";
163             }
164             }
165 11         22 return $self;
166             }
167              
168             =head2 parse_base
169              
170             Base method for parsing.
171              
172             =cut
173              
174             sub parse_base {
175 29     29 1 41 my $self = shift;
176 29         45 my $spec = shift;
177 29         92 my %arg = @_;
178              
179 29         52 my $meth = delete $arg{method};
180 29         45 my $type = delete $arg{type};
181              
182             # Capital letter means a class of some sort. Arbitrary but at least it's simple.
183 29         66 my $extType = ucfirst($type) eq $type;
184              
185 29 100       136 if(ref $spec ~~ 'ARRAY') {
    100          
    50          
186 3 50       16 $self->$meth->push($extType ? $type->new($_) : $_) foreach @$spec;
187             } elsif(ref $spec ~~ 'HASH') {
188             # FIXME wrong if $extType is not set?
189 8 50       38 $self->$meth->push($extType ? $type->new($spec) : $spec);
190             } elsif(ref $spec) {
191 0         0 $self->$meth->push($spec);
192             # die "Don't know how to handle ref $spec for $meth";
193             } else {
194 18 50       87 $self->$meth->push($extType ? $type->new($spec) : $spec);
195             }
196 29         897 return $self;
197             }
198              
199              
200             =head2 reclassify
201              
202             Virtual method to allow subclass to perform any required updates after reblessing to an alternative class.
203              
204             =cut
205              
206 11     11 1 26 sub reclassify { $_[0] }
207              
208              
209             =head2 upgradeTo
210              
211             Upgrade an existing L object to a subclass.
212              
213             =cut
214              
215             sub upgradeTo {
216 11     11 1 23 my $self = shift;
217 11         17 my $class = shift;
218 11         28 bless $self, $class;
219 11         51 $self->reclassify;
220             }
221              
222             =head2 parse_limit
223              
224             Handle a 'limit' directive.
225              
226             =cut
227              
228             sub parse_limit {
229 1     1 1 2 my $self = shift;
230 1         2 my $v = shift;
231 1         8 $self->limit($v);
232 1         10 return $self;
233             }
234              
235             =head2 parse_group
236              
237             =cut
238              
239             sub parse_group {
240 4     4 1 6 my $self = shift;
241 4         20 $self->parse_base(
242             @_,
243             method => 'group',
244             type => 'EntityModel::Query::GroupField'
245             );
246             }
247              
248             =head2 parse_where
249              
250             =cut
251              
252             sub parse_where {
253 6     6 1 10 my $self = shift;
254 6         38 $self->where(EntityModel::Query::Condition->new(@_));
255 6         58 return $self;
256             }
257              
258             =head2 typeSQL
259              
260             Proxy method for L, returns the SQL string representation for the current query type (such as 'select' or 'insert into').
261              
262             =cut
263              
264 16     16 1 42 sub typeSQL { shift->type }
265              
266             =head2 fieldsSQL
267              
268             Generate the SQL for fields.
269              
270             =cut
271              
272             sub fieldsSQL {
273 16     16 1 18 my $self = shift;
274 20 100       278 my $fields = join(', ', map {
275 16         48 $_->asString . ($_->alias ? (' as ' . $_->alias) : '');
276             } $self->field->list);
277 16 50       201 return unless $fields;
278 16         28 return $fields;
279             }
280              
281             =head2 fromSQL
282              
283             SQL for the 'from' clause.
284              
285             =cut
286              
287             sub fromSQL {
288 16     16 1 20 my $self = shift;
289 16         88 my $from = join(', ', map { $_->asString } $self->from->list);
  14         179  
290 16 100       169 return unless $from;
291 14         52 logDebug("From " . $from);
292 14         541 return 'from ' . $from;
293             }
294              
295             =head2 limitSQL
296              
297             =cut
298              
299             sub limitSQL {
300 16     16 1 22 my $self = shift;
301 16 100       38 my $sql = (exists $self->{limit}) ? ('limit ' . $self->limit) : '';
302 16 100       44 return unless $sql;
303 2         4 return $sql;
304             }
305              
306             =head2 offsetSQL
307              
308             =cut
309              
310             sub offsetSQL {
311 16     16 1 17 my $self = shift;
312 16 50       39 my $sql = (exists $self->{offset}) ? ('offset ' . $self->offset) : '';
313 16 50       32 return unless $sql;
314 0         0 return $sql;
315             }
316              
317             =head2 orderSQL
318              
319             =cut
320              
321             sub orderSQL {
322 16     16 1 19 my $self = shift;
323 16         46 my $sql = join(', ', map { $_->asString } $self->order->list);
  4         46  
324 16 100       808 return unless $sql;
325 4         6 return 'order by ' . $sql;
326             }
327              
328             =head2 groupSQL
329              
330             =cut
331              
332             sub groupSQL {
333 16     16 1 21 my $self = shift;
334 16         51 my $sql = join(', ', map { $_->asString } $self->group->list);
  8         473  
335 16 100       507 return unless $sql;
336 8         17 return 'group by ' . $sql;
337             }
338              
339             =head2 havingSQL
340              
341             =cut
342              
343             sub havingSQL {
344 16     16 1 20 my $self = shift;
345 16         56 my $sql = join(', ', map { $_->expr } $self->having->list);
  0         0  
346 16 50       1322 return unless $sql;
347 0         0 return $sql;
348             }
349              
350             =head2 whereSQL
351              
352             =cut
353              
354             sub whereSQL {
355 18     18 1 28 my $self = shift;
356 18 100       55 return unless $self->where;
357 12         58 return [ "where ", @{ $self->where->inlineSQL } ];
  12         30  
358             }
359              
360             =head2 joinSQL
361              
362             =cut
363              
364             sub joinSQL {
365 16     16 1 22 my $self = shift;
366 16         215 my $join = join(' ', map { $_->asString } $self->join->list);
  0         0  
367 16 50       1418 return unless $join;
368 0         0 return $join;
369             }
370              
371             =head2 keyword_order { qw{type fields from join where having group order offset limit};
372              
373             =cut
374              
375 0     0 1 0 sub keyword_order { qw{type fields from join where having group order offset limit}; }
376              
377             =head2 inlineSQL
378              
379             =cut
380              
381             sub inlineSQL {
382 16     16 1 19 my $self = shift;
383 16         20 my @sql;
384 16         46 my @items = $self->keyword_order;
385             ITEM:
386 16         44 while(@items) {
387 160         249 my $m = shift(@items) . 'SQL';
388 160         939 my $entry = $self->$m;
389 160 100       613 next ITEM unless defined $entry;
390 70 100       142 push @sql, ' ' if @sql;
391 70 100       3162 if(ref $entry eq 'ARRAY') {
392 10         36 push @sql, @$entry;
393             } else {
394 60         299 push @sql, $entry;
395             }
396             }
397 16 50       47 push @sql, ' for update' if $self->forUpdate;
398 16         108 return $self->normaliseInlineSQL(@sql);
399             }
400              
401             =head2 results
402              
403             =cut
404              
405             sub results {
406 0     0 1   my $self = shift;
407 0           logDebug("Running [%s]", $self->sqlString);
408              
409 0 0         $self->db(EntityModel::DB->active_db) unless $self->db;
410 0 0         die "No DB" unless $self->db;
411              
412 0           my ($sql, @bind) = $self->sqlAndParameters;
413 0           my $sth;
414 0           eval {
415 0           $sth = $self->dbh->prepare($sql);
416 0           $sth->execute(@bind);
417             };
418 0 0         if($@) {
419 0           my $msg = $@;
420 0           logError("Query [%s] failed: [%s]", $sql, $msg);
421 0           return EntityModel::Error->new('SQL failed');
422             }
423              
424 0 0         return EntityModel::Error->new('Invalid handle') unless $sth->{Active};
425              
426 0           my @rslt;
427 0           while(my $row = $sth->fetchrow_hashref) {
428 0   0       logDebug("Got " . join(',', map { $_ . ' => ' . ($row->{$_} // 'undef') } keys %$row));
  0            
429 0           push @rslt, $row;
430             }
431 0           return @rslt;
432             }
433              
434             =head2 iterate
435              
436             Calls the given method for each result returned from the current query.
437              
438             =cut
439              
440             sub iterate {
441 0     0 1   my $self = shift;
442 0           my $code = shift;
443 0           logDebug("Running [%s]", $self->sqlString);
444              
445 0           my ($sql, @bind) = $self->sqlAndParameters;
446              
447 0           my $sth;
448 0           eval {
449 0           $sth = $self->dbh->prepare($sql);
450 0           $sth->execute(@bind);
451             };
452 0 0         if($@) {
453 0           my $msg = $@;
454 0           logError("Query [%s] failed: [%s]", $sql, $msg);
455 0           return EntityModel::Error->new('SQL failed');
456             }
457 0 0         return unless $sth->{Active};
458              
459 0           while(my $row = $sth->fetchrow_hashref) {
460 0   0       logDebug("Got " . join(',', map { $_ . ' => ' . ($row->{$_} // 'undef') } keys %$row));
  0            
461 0           $code->($row);
462             }
463 0           return $self;
464             }
465              
466             =head2 dbh
467              
468             =cut
469              
470             sub dbh {
471 0     0 1   my $self = shift;
472 0 0         $self->db(EntityModel::DB->active_db) unless $self->db;
473 0           return $self->db->dbh;
474             }
475              
476             1;
477              
478             __END__