File Coverage

blib/lib/ORMesque.pm
Criterion Covered Total %
statement 30 305 9.8
branch 0 122 0.0
condition 0 48 0.0
subroutine 10 52 19.2
pod 38 38 100.0
total 78 565 13.8


line stmt bran cond sub pod time code
1             #ABSTRACT: Lightweight To-The-Point ORM
2              
3             package ORMesque;
4              
5 2     2   32079 use strict;
  2         5  
  2         73  
6 2     2   11 use warnings;
  2         4  
  2         59  
7 2     2   1027 use ORMesque::DBIxSimpleHack; # mod'd dbix-simple to use S::A::L
  2         6  
  2         73  
8 2     2   18 use base 'DBIx::Simple';
  2         4  
  2         2143  
9              
10 2     2   14059 use ORMesque::SchemaLoader;
  2         7  
  2         60  
11              
12 2     2   2680 use SQL::Abstract;
  2         26517  
  2         84  
13 2     2   2663 use SQL::Interp;
  2         59957  
  2         17  
14              
15 2     2   3759 use Data::Page;
  2         18408  
  2         20  
16              
17             our $Cache = undef;
18              
19             our $VERSION = 1.110422;# VERSION
20              
21              
22             sub new {
23 0     0 1   my $class = shift;
24              
25 0 0         return $Cache if $Cache;
26              
27 0           my @dsn = @_;
28 0           my $nsp = undef;
29            
30             # check if a namespace has been defined
31 0 0         if (ref $dsn[$#dsn]) {
32 0 0         if (defined $dsn[$#dsn]->{NameSpace}) {
33 0           $nsp = $dsn[$#dsn]->{NameSpace};
34 0           delete $dsn[$#dsn]->{NameSpace};
35             }
36             }
37              
38 0 0         my $dbh = ORMesque->connect(@dsn) or die $DBI::errstr;
39 0           my $cfg = {driver => $dbh->{dbh}->get_info(17)};
40              
41 0 0         die "Can't make out your database driver" unless $cfg->{driver};
42              
43 0           my $self = {};
44 0           my $this = {};
45              
46 0           bless $self, $class;
47            
48             # explicitly set the namespace
49 0 0         defined $nsp ? $self->namespace($nsp) : $self->namespace($class);
50              
51 0 0         warn "Error connecting to the database..." unless $dbh;
52 0 0         warn "No database driver specified in the configuration file"
53             unless $cfg->{driver};
54              
55             # POSTGRESQL CONFIGURATION
56 0 0         $this = ORMesque::SchemaLoader->new($dbh->{dbh})->mysql
57             if lc($cfg->{driver}) =~ '^postgre(s)?(ql)?$';
58              
59             # MYSQL CONFIGURATION
60 0 0         $this = ORMesque::SchemaLoader->new($dbh->{dbh})->mysql
61             if lc($cfg->{driver}) eq 'mysql';
62              
63             # SQLite CONFIGURATION
64 0 0         $this = ORMesque::SchemaLoader->new($dbh->{dbh})->sqlite
65             if lc($cfg->{driver}) eq 'sqlite';
66              
67 0           $self->{schema} = $this->{schema};
68 0           die "Could not read the specified database $cfg->{driver}"
69 0 0         unless @{$self->{schema}->{tables}};
70              
71             # setup reuseable connection using DBIx::Simple
72 0 0         $self->{dbh} = DBIx::Simple->connect($dbh->{dbh}) or die DBIx::Simple->error;
73 0           $self->{dbh}->result_class = 'DBIx::Simple::Result';
74 0           $self->{dsn} = [@dsn];
75              
76             # define defaults
77 0           $self->{target} = '';
78              
79             # create base accessors
80 2     2   866 no warnings 'redefine';
  2         5  
  2         102  
81 2     2   10 no strict 'refs';
  2         4  
  2         7810  
82              
83 0           foreach my $table (@{$self->{schema}->{tables}}) {
  0            
84              
85 0           my $class = $self->namespace;
86 0           my $method = $class . "::" . lc $table;
87 0           my $classtable = $table;
88            
89 0 0         if ($classtable =~ /[\-\_]/) {
90 0           $classtable = join '', map { ucfirst lc $_ }
  0            
91             split /[\-\_]/, $classtable;
92             }
93             else {
94 0           $classtable = ucfirst lc $classtable;
95             }
96            
97 0           my $package_name = $class . "::" . $classtable;
98 0           my $package = "package $package_name;" . q|
99            
100             use base '| . $class . q|';
101            
102             sub new {
103             my ($class, $base, $table) = @_;
104             my $self = {};
105             bless $self, $class;
106             $self->{table} = $table;
107             $self->{columns} = $base->{schema}->{table}->{$table}->{columns};
108             $self->{where} = {};
109             $self->{order} = [];
110             $self->{key} = $base->{schema}->{table}->{$table}->{primary_key};
111             $self->{collection} = [];
112             $self->{cursor} = 0;
113             $self->{current} = {};
114             $self->{namespace} = $base->{namespace};
115             $self->{schema} = $base->{schema};
116             $self->{dbh} = $base->dbix();
117             $self->{dsn} = $base->{dsn};
118            
119             # build database objects
120             $self->{configuration} = $cfg;
121            
122             foreach my $column (@{$self->{schema}->{table}->{$table}->{columns}}) {
123             $self->{current}->{$column} = '';
124             my $attribute = $class . "::" . $column;
125             *{$attribute} = sub {
126             my ($self, $data) = @_;
127             if (defined $data) {
128             $self->{current}->{$column} = $data;
129             return $data;
130             }
131             else {
132             return
133             $self->{current}->{$column};
134             }
135             };
136             }
137            
138             return $self;
139             }
140             1;
141             |;
142 0           eval $package;
143 0 0         die print $@ if $@; # debugging
144 0           *{$method} = sub {
145 0     0     return $package_name->new($self, $table);
146 0           };
147              
148             # build dbo table
149              
150             }
151              
152 0           $Cache = $self;
153 0           return $self;
154             }
155              
156             sub _protect_sql {
157 0     0     my ($dbo, @sql) = @_;
158            
159 0 0         return @_ unless $dbo->{schema}->{escape_string};
160            
161             # set field delimiters
162 0 0         my ($stag, $etag) =
163             length($dbo->{schema}->{escape_string}) == 1
164             ? ($dbo->{schema}->{escape_string}, $dbo->{schema}->{escape_string})
165             : split //, $dbo->{schema}->{escape_string};
166            
167 0           my $params = {};
168              
169 0 0         if ("HASH" eq ref $sql[0]) {
170             $params = {
171             map {
172 0 0         if ($_ =~ /[^a-zA-Z\_0-9\s]/) {
  0            
173 0           ( $_ => $sql[0]->{$_} )
174             }
175             else {
176 0           ( "$stag$_$etag" => $sql[0]->{$_} )
177             }
178 0           } keys %{$sql[0]}
179             };
180             }
181             else {
182             $params = [ map {
183 0 0         if ($_ =~ /[^a-zA-Z\_0-9\s]/) {
  0            
184 0           ($_)
185             }
186             else {
187 0           "$stag$_$etag"
188             }
189             } @sql ];
190             }
191            
192 0 0         return "ARRAY" eq ref $params ? ( @{ $params } ) : $params;
  0            
193             }
194              
195              
196             sub namespace {
197 0     0 1   my ($dbo, $namespace) = @_;
198 0 0         $dbo->{namespace} = $namespace if defined $namespace;
199 0           return $dbo->{namespace};
200             }
201              
202              
203             sub reset {
204 0     0 1   $Cache = undef;
205             }
206              
207              
208             sub next {
209 0     0 1   my $dbo = shift;
210            
211 0   0       $dbo->{collection} ||= [];
212            
213 0           my $next =
214 0 0         $dbo->{cursor} <= (scalar(@{$dbo->{collection}}) - 1) ? $dbo : undef;
215 0   0       $dbo->{current} = $dbo->{collection}->[$dbo->{cursor}] || {};
216 0           $dbo->{cursor}++;
217              
218 0           return $next;
219             }
220              
221              
222             sub first {
223 0     0 1   my $dbo = shift;
224              
225 0           $dbo->{cursor} = 0;
226 0   0       $dbo->{current} = $dbo->{collection}->[0] || {};
227              
228 0           return $dbo->current;
229             }
230              
231              
232             sub last {
233 0     0 1   my $dbo = shift;
234              
235 0           $dbo->{cursor} = (scalar(@{$dbo->{collection}}) - 1);
  0            
236 0   0       $dbo->{current} = $dbo->{collection}->[$dbo->{cursor}] || {};
237              
238 0           return $dbo->current;
239             }
240              
241              
242             sub collection {
243 0     0 1   return shift->{collection};
244             }
245              
246              
247             sub current {
248 0     0 1   return shift->{current};
249             }
250              
251              
252             sub clear {
253 0     0 1   my $dbo = shift;
254              
255 0           foreach my $column (keys %{$dbo->{current}}) {
  0            
256 0           $dbo->{current}->{$column} = '';
257             }
258              
259 0           $dbo->{collection} = [];
260              
261 0           return $dbo;
262             }
263              
264              
265             sub key {
266 0     0 1   shift->{key};
267             }
268              
269              
270             sub select {
271 0     0 1   my $dbo = shift;
272              
273 0 0         $dbo->{select} = [@_] if @_;
274              
275 0           return $dbo;
276             }
277              
278              
279             sub return {
280 0     0 1   my $dbo = shift;
281 0           my %where = %{$dbo->current};
  0            
282              
283 0 0         delete $where{$dbo->key} if $dbo->key;
284              
285 0           $dbo->read(\%where)->last;
286              
287 0           return $dbo;
288             }
289              
290              
291             sub count {
292 0     0 1   my $dbo = shift;
293 0           my $whr = shift;
294            
295 0 0         if (defined $whr) {
296 0   0       my @columns = $dbo->_protect_sql($dbo->key || '*');
297 0           my $counter = 'COUNT('. $columns[0] .')';
298 0           $dbo->select($counter)->read($whr);
299 0           return scalar $dbo->list;
300             }
301            
302 0           return scalar @{$dbo->{collection}};
  0            
303             }
304              
305              
306             sub create {
307 0     0 1   my $dbo = shift;
308 0   0       my $input = shift || {};
309 0           my @columns = $dbo->_protect_sql(@{$dbo->{columns}});
  0            
310              
311 0           die
312             "Cannot create an entry in table ($dbo->{table}) without any input parameters."
313 0 0         unless keys %{$input};
314              
315             # add where clause to current for
316             # $dbo->create(..); $dbo->return; operations
317 0 0         if ($input) {
318 0           foreach my $i (keys %{$input}) {
  0            
319 0 0         if (defined $dbo->{current}->{$i}) {
320 0           $dbo->{current}->{$i} = $input->{$i};
321             }
322             }
323             }
324              
325             # insert
326             $dbo->dbix
327 0           ->insert($dbo->_protect_sql($dbo->{table}), $dbo->_protect_sql($input));
328              
329 0 0         return $dbo->error ? 0 : 1;
330             }
331              
332              
333             sub read {
334 0     0 1   my $dbo = shift;
335              
336 0           my $tables = [];
337              
338 0 0         if ("ARRAY" eq ref $_[0]) {
339 0           $tables = shift;
340             }
341              
342 0   0       my $where = shift || {};
343 0   0       my $order = shift || [];
344 0           my $table = $dbo->{table};
345 0           my @columns = ();
346              
347 0           my ($limit, $offset) = @_;
348              
349 0 0         if (defined $dbo->{select}) {
350 0           @columns = $dbo->_protect_sql(@{$dbo->{select}});
  0            
351             }
352             else {
353 0           @columns = $dbo->_protect_sql(@{$dbo->{columns}});
  0            
354             }
355              
356             # generate a where primary_key = ? clause
357 0 0 0       if ($where && ref($where) ne "HASH") {
358 0           $where = {$dbo->key => $where};
359             }
360              
361 0 0 0       if ($limit || $offset || $dbo->{ispaged}) {
      0        
362              
363 0 0         if ($dbo->{ispaged}) {
364 0           $dbo->{ispaged} = 0;
365 0           $dbo->pager->total_entries($dbo->dbix
366             ->select($table, 'COUNT(*)', $dbo->_protect_sql($where))
367             ->array->[0]);
368 0           ($offset, $limit) =
369             ($dbo->pager->skipped, $dbo->pager->entries_per_page);
370             }
371              
372             $dbo->{resultset} = sub {
373 0           return $dbo->dbix->select(
374             join(',',
375             $dbo->_protect_sql($table),
376 0     0     map { $dbo->_protect_sql($_) } @{$tables}),
  0            
377             \@columns,
378             $dbo->_protect_sql($where),
379             $order, $limit, $offset
380             );
381 0           };
382             }
383             else {
384             $dbo->{resultset} = sub {
385 0           return $dbo->dbix->select(
386             join(',',
387             $dbo->_protect_sql($table),
388 0     0     map { $dbo->_protect_sql($_) } @{$tables}),
  0            
389             \@columns,
390             $dbo->_protect_sql($where),
391             $order
392             );
393 0           };
394             }
395              
396 0 0         if (defined $dbo->{select}) {
397            
398             # create a fiticious collection :/
399            
400 0           $dbo->{collection} = [
401              
402             map {
403 0           foreach my $i (keys %{$dbo->{current}})
  0            
404             {
405 0 0         unless (defined $_->{$i}) {
406 0           $_->{$i} = '';
407             }
408             }
409              
410             $_
411 0           }
412              
413 0           @{$dbo->{resultset}->()->hashes}
414             ];
415            
416             }
417             else {
418 0           $dbo->{collection} = $dbo->{resultset}->()->hashes;
419             }
420              
421 0           $dbo->{cursor} = 0;
422 0           $dbo->next;
423            
424 0 0         $dbo->{select} = undef if defined $dbo->{select};
425              
426 0 0         return $dbo->error ? 0 : $dbo;
427             }
428              
429              
430             sub update {
431 0     0 1   my $dbo = shift;
432 0   0       my $input = shift || {};
433 0   0       my $where = shift || {};
434 0           my $table = $dbo->{table};
435 0           my @columns = $dbo->_protect_sql(@{$dbo->{columns}});
  0            
436              
437             # process direct input
438 0           die
439             "Attempting to update an entry in table ($dbo->$table) without any input."
440 0 0         unless keys %{$input};
441              
442             # generate a where primary_key = ? clause
443 0 0 0       if ($where && ref($where) ne "HASH") {
444 0           $where = {$dbo->key => $where};
445             }
446              
447             $dbo->dbix->update(
448 0           $dbo->_protect_sql($table),
449             $dbo->_protect_sql($input),
450             $dbo->_protect_sql($where)
451 0 0         ) if keys %{$input};
452              
453 0 0         return $dbo->error ? 0 : 1;
454             }
455              
456              
457             sub delete {
458 0     0 1   my $dbo = shift;
459 0   0       my $where = shift || {};
460 0           my $table = $dbo->{table};
461              
462             # process where clause
463 0 0 0       if (ref($where) eq "HASH") { }
    0 0        
464             elsif ($where && $dbo->key && ref($where) ne "HASH") {
465 0           $where = {$dbo->key => $where};
466             }
467             else {
468 0           die "Cannot delete without a proper where clause, "
469             . "use delete_all to purge the entire database table";
470             }
471              
472 0           $dbo->dbix
473             ->delete($dbo->_protect_sql($table), $dbo->_protect_sql($where));
474              
475 0 0         return $dbo->error ? 0 : 1;
476             }
477              
478              
479             sub delete_all {
480 0     0 1   my $dbo = shift;
481 0           my $table = $dbo->{table};
482              
483 0           $dbo->dbix->delete($dbo->_protect_sql($table));
484              
485 0 0         return $dbo->error ? 0 : 1;
486             }
487              
488              
489             sub join {
490 0     0 1   my $dbo = shift;
491              
492 0 0         die 'Join is meant to be called on ORMesque objects with table definitions'
493             unless $dbo->{table};
494              
495 0           my @objs = @_;
496 0           my $rs = [];
497 0           my $q = 0;
498              
499 0           unshift @objs, $dbo;
500              
501 0           my @tmps = ();
502              
503 0           for (my $i = 0; $i < @objs; $i++) {
504 0 0         if ("HASH" eq ref $objs[$i]) {
505 0           $tmps[$#tmps]->{join_configuration} = $objs[$i];
506             }
507             else {
508 0           $objs[$i]->{join_configuration} = {};
509 0           push @tmps, $objs[$i];
510             }
511             }
512              
513 0           @objs = @tmps;
514              
515 0 0         if (@objs > 1) {
516 0           foreach my $obj (@objs) {
517 0 0 0       die 'Invalid ORMesque object passed to join'
518             unless $obj->{table} && $obj->{collection};
519             }
520              
521             # use the first object to set the length of the aggregator
522 0           for (my $i = 0; $i < scalar(@{$objs[0]->{collection}}); $i++) {
  0            
523 0           my $aggregate = {};
524 0           for (my $y = 0; $y < @objs; $y++) {
525 0           my $cfg = $objs[$y]->{join_configuration};
526 0           my $rec = $objs[$y]->{collection}->[$i];
527 0 0         if (keys %{$objs[$y]->{join_configuration}}) {
  0            
528 0 0         if ($cfg->{persist}) {
529 0           $rec = $objs[$y]->{collection}->[0];
530             }
531             }
532 0           my $new = {
533 0 0         map { $objs[$y]->{table} . "_" . $_ => $rec->{$_} }
534 0           keys %{($rec || $objs[$y]->{current})}
535             };
536 0 0         if ($cfg->{columns}) {
537 0           my $xchg = {};
538 0           foreach (keys %{$cfg->{columns}}) {
  0            
539 0           $xchg->{$cfg->{columns}->{$_}} = $new->{$_};
540             }
541 0           $new = $xchg;
542             }
543 0           $aggregate = {%{$aggregate}, %{$new}};
  0            
  0            
544             }
545 0           $rs->[$q++] = $aggregate;
546             }
547 0           return $rs;
548             }
549             else {
550 0           die 'Please supply two or more ORMesque objects which may include the '
551             . 'invoking object (self) before performing a join';
552             }
553             }
554              
555              
556             sub page {
557 0     0 1   my $dbo = shift;
558 0 0         die 'The page method requires a page number and number of rows to return'
559             unless @_ == 2;
560              
561 0           $dbo->{ispaged} = 1;
562              
563 0           $dbo->pager->current_page($_[0]);
564 0           $dbo->pager->entries_per_page($_[1]);
565              
566 0           return $dbo;
567             }
568              
569              
570             sub pager {
571 0     0 1   my $dbo = shift;
572 0   0       $dbo->{pager} ||= Data::Page->new(@_);
573             }
574              
575              
576              
577             sub columns {
578 0     0 1   shift->{resultset}->()->columns(@_);
579             }
580              
581              
582             sub into {
583 0     0 1   return shift->{resultset}->()->into(@_);
584             }
585              
586              
587             sub list {
588 0     0 1   return shift->{resultset}->()->list(@_);
589             }
590              
591              
592             sub array {
593 0     0 1   return shift->{resultset}->()->array(@_);
594             }
595              
596              
597             sub hash {
598 0     0 1   return shift->{resultset}->()->hash(@_);
599             }
600              
601              
602             sub flat {
603 0     0 1   return shift->{resultset}->()->flat(@_);
604             }
605              
606              
607             sub arrays {
608 0     0 1   return shift->{resultset}->()->arrays(@_);
609             }
610              
611              
612             sub hashes {
613 0     0 1   return shift->{resultset}->()->hashes(@_);
614             }
615              
616              
617             sub map_hashes {
618 0     0 1   return shift->{resultset}->()->map_hashes(@_);
619             }
620              
621              
622             sub map_arrays {
623 0     0 1   return shift->{resultset}->()->map_arrays(@_);
624             }
625              
626              
627             sub rows {
628 0     0 1   return shift->{resultset}->()->rows(@_);
629             }
630              
631              
632             sub error {
633 0     0 1   my $dbo = shift;
634 0           my $err = $dbo->{dbh}->error(@_);
635 0           $err =~ s/^DBI error\:\s+//;
636 0           $err =~ s/\n+/\, /g;
637 0           return $err;
638             }
639              
640              
641             sub query {
642 0     0 1   return shift->dbix->query(@_);
643             }
644              
645              
646             sub iquery {
647 0     0 1   return shift->dbix->iquery(@_);
648             }
649              
650              
651             sub dbix {
652 0     0 1   my $dbo = shift;
653            
654 0   0       $dbo->{last_chk} ||= 0;
655            
656 0 0         if ((time - $dbo->{last_chk}) < 10) {
657 0           return $dbo->{dbh};
658             }
659             else {
660 0 0         if ($dbo->connected) {
661 0           return $dbo->{dbh};
662             }
663             else {
664 0 0         $dbo->{dbh} = DBIx::Simple->connect(@{$dbo->{dsn}})
  0            
665             or die DBIx::Simple->error;
666 0           $dbo->{last_chk} = time;
667             }
668             }
669              
670 0           return $dbo->{dbh};
671             }
672              
673              
674             sub dbi {
675 0     0 1   return shift->dbix->{dbh};
676             }
677              
678              
679             sub connected {
680 0     0 1   my $dbo = shift;
681 0 0         return unless $dbo->{dbh}->{dbh};
682 0 0         if (int($dbo->{dbh}->{dbh}->ping)) {
683 0           return 1;
684             }
685             else {
686 0           my $ok = 0;
687 0           eval { $ok = $dbo->{dbh}->{dbh}->do('select 1') };
  0            
688 0           return $ok;
689             }
690 0           return 0;
691             }
692              
693             1;
694              
695             __END__