File Coverage

blib/lib/DBomb/Base/Private.pm
Criterion Covered Total %
statement 16 18 88.8
branch n/a
condition n/a
subroutine 6 6 100.0
pod n/a
total 22 24 91.6


line stmt bran cond sub pod time code
1             package DBomb::Base::Private;
2              
3             =head1 NAME
4              
5             DBomb::Base::Private - The private API for DBomb::Base
6              
7             =head1 SYNOPSIS
8              
9             =cut
10              
11 1     1   1758 use strict;
  1         3  
  1         42  
12 1     1   6 use warnings;
  1         2  
  1         46  
13             our $VERSION = '$Revision: 1.20 $';
14              
15 1     1   5 use Carp::Assert;
  1         2  
  1         10  
16 1     1   168 use Carp qw(croak);
  1         3  
  1         61  
17 1     1   5 use DBomb::Query;
  1         2  
  1         26  
18 1     1   83 use DBomb::GluedQuery;
  0            
  0            
19             use DBomb::GluedUpdate;
20             use DBomb::Query::Update;
21             use DBomb::Query::Insert;
22             use DBomb::Query::Delete;
23             use DBomb::Value::Column;
24             use DBomb::Util;
25             use DBomb::Tie::PrimaryKeyList;
26             use base qw(DBomb::DBH::Owner DBomb::Base::Defs);
27              
28             use Class::MethodMaker
29             get_set => [qw(_dbo_values), ## { column_name => value_obj }
30             ];
31              
32             __PACKAGE__->mk_classdata('_dbo_table_info');
33             #__PACKAGE__->mk_classdata('_dbo_sth');
34              
35             ## new()
36             ## new($PrimaryKeyValue)
37             ## new($pk_column)
38             ## new($dbh)
39              
40             ## Meant to be overridden by subclasses.
41              
42             ## returns a query object
43             ## $class->select(@column_aliases_or_names)
44              
45              
46             ## $class->selectall_arrayref()
47             ## $class->selectall_arrayref(@bind_values)
48             ## $class->selectall_arrayref($dbh, @bind_values)
49              
50             ## _dbo_column_accessor($column_info)
51             sub _dbo_column_accessor
52             {
53             my $self = shift;
54             my $col = shift;
55              
56             assert(UNIVERSAL::isa($col,'DBomb::Meta::ColumnInfo'), 'column accessor requires a column info object');
57              
58             if (@_){
59             $self->_dbo_column_accessor_set($col,@_);
60             }
61              
62             $self->_dbo_column_accessor_get($col);
63             }
64              
65             sub _dbo_column_accessor_get
66             {
67             my ($self,$col) = @_;
68             assert(UNIVERSAL::isa($col,'DBomb::Meta::ColumnInfo'), 'column accessor requires a column info object');
69              
70             my $v = $self->_dbo_values->{$col->name};
71              
72             ## if a value exists, return it.
73             return $v->value if $v->has_value;
74              
75             ## auto-fetch
76             $self->_dbo_fetch_columns($self->_dbo_expand_select_groups([$col]));
77              
78             croak "bug: fetched column '@{[$col->fq_name]}' but value did not get set. This happens when the object has no PK." unless $v->has_value;
79             return $v->value;
80             }
81              
82             sub _dbo_column_accessor_set
83             {
84             my ($self,$cinfo,$data) = @_;
85              
86             assert(UNIVERSAL::isa($cinfo,'DBomb::Meta::ColumnInfo'), 'column accessor requires a column info object');
87              
88             $self->_dbo_values->{$cinfo->name}->value($data);
89              
90             #TODO: auto-update?
91             }
92              
93             ## access a column that is part of one or more has_a relationships
94             sub _dbo_has_a_column_accessor
95             {
96             my $self = shift;
97             my $cinfo = shift;
98             $self->_dbo_has_a_column_accessor_set($cinfo,@_) if @_;
99             $self->_dbo_column_accessor_get($cinfo,@_); ## A regular get.
100             }
101              
102             ## access a column that is part of one or more has_a relationships
103             sub _dbo_has_a_column_accessor_set
104             {
105             my ($self,$cinfo,$data) = @_;
106             assert(@_ == 3, 'column accessor expects exactly one parameter');
107              
108             my $v = $self->_dbo_values->{$cinfo->name};
109             return if $v->has_value && DBomb::Util::is_same_value($v->value, $data);
110              
111             ## set it
112             $v->value($data);
113              
114             ## discard any has_a-related objects we have that are based on this column
115             for my $has_a (@{$cinfo->table_info->has_as}){
116             next unless exists $has_a->one_to_many->many_key->columns->{$cinfo->name};
117             $self->{$has_a->attr} = undef;
118             }
119             }
120              
121             ## access a has_a object (_not_ the same things as a FK column.)
122             sub _dbo_has_a_accessor
123             {
124             my $self = shift;
125             my $has_a = shift;
126             $self->_dbo_has_a_accessor_set($has_a,@_) if @_;
127             $self->_dbo_has_a_accessor_get($has_a);
128             }
129              
130             ## They want an object based on a has_a relationship
131             sub _dbo_has_a_accessor_get
132             {
133             my ($self, $has_a) = @_;
134             assert(@_ == 2);
135              
136             ## See if we already have it
137             my $v = $self->{$has_a->attr};
138             return $v if defined $v;
139              
140             ## Ok, produce an object if we have the all necessary values
141             my $key = [];
142             my ($one_key, $many_key) = ($has_a->one_to_many->one_key, $has_a->one_to_many->many_key);
143             my $dbo_values = $self->_dbo_values;
144              
145             for my $cinfo (values %{$many_key->columns}){
146              
147             if (not $dbo_values->{$cinfo->name}->has_value){
148             # fk_column doesn't have a value, get it.
149             my $accessor = $cinfo->accessor;
150             $self->$accessor;
151             }
152              
153             if (not defined $dbo_values->{$cinfo->name}->value){
154             ## fk value (NULL).
155             return undef;
156             }
157             push @$key, $dbo_values->{$cinfo->name}->value;
158             }
159              
160             my $f_class = $one_key->table_info->class;
161             my $new_object = $f_class->new(new DBomb::Value::Key($one_key,$key));
162              
163             ## Store it for next time.
164             $self->{$has_a->attr} = $new_object;
165              
166             return $new_object;
167             }
168              
169             ## Set the has_a value... what should this do?
170             sub _dbo_has_a_accessor_set
171             {
172             my ($self, $has_a, @args) = @_;
173             die "not yet implemented";
174             }
175              
176             ## Access the list of referring objects in a has_query relationship
177             sub _dbo_has_query_accessor
178             {
179             my $self = shift;
180             my $has_query = shift;
181             assert(UNIVERSAL::isa($has_query, 'DBomb::Meta::HasQuery'),'_dbo_has_query_accessor requires a query object');
182              
183             $self->_dbo_has_query_accessor_set($has_query,@_) if @_;
184             $self->_dbo_has_query_accessor_get($has_query);
185             }
186              
187             sub _dbo_has_query_accessor_get
188             {
189             my $self = shift;
190             my $has_query = shift;
191              
192             assert(UNIVERSAL::isa($self,__PACKAGE__));
193             assert(UNIVERSAL::isa($has_query, 'DBomb::Meta::HasQuery'),'_dbo_has_query_accessor requires a query object');
194             assert(@_ == 0);
195              
196             ## return our local copy if we have it
197             return $self->{$has_query->attr} if defined $self->{$has_query->attr};
198              
199             ##
200             my @bind_values;
201             for (@{$has_query->bind_subs}){
202             push @bind_values, $_->($self,$has_query->query);
203             }
204              
205             my $keys_list = $has_query->query->selectall_arrayref($self->_dbo_dbh, @bind_values);
206              
207             ## vivify those objects
208             my $obj_class = $has_query->f_table->class;
209             my @arr;
210             tie @arr, 'DBomb::Tie::PrimaryKeyList', $obj_class, $keys_list;
211              
212             $self->{$has_query->attr} = \@arr;
213             }
214              
215             ## ... what should this do?
216             sub _dbo_has_query_accessor_set
217             {
218             die "set has_query list not implemented";
219             }
220              
221             ## Access the list of referring objects in a has_many relationship
222             sub _dbo_has_many_accessor
223             {
224             my $self = shift;
225             my $has_many = shift;
226             assert(UNIVERSAL::isa($has_many, 'DBomb::Meta::HasMany'),'_dbo_has_many_accessor requires a has_many object');
227              
228             if (@_) {
229             ## Since undef is the only allowed value currently, we don't want to immediately trigger a 'get'.
230             ## which would fuck up the cached.
231             $self->_dbo_has_many_accessor_set($has_many,@_)
232             }
233             else {
234             $self->_dbo_has_many_accessor_get($has_many);
235             }
236             }
237              
238             ## Get the list of objects.
239             sub _dbo_has_many_accessor_get
240             {
241             my $self = shift;
242             my $has_many = shift;
243             assert(@_ == 0, 'parameter count');
244             assert(UNIVERSAL::isa($has_many, 'DBomb::Meta::HasMany'),'_dbo_has_many_accessor_get requires a has_many object');
245             assert(defined($self->_dbo_dbh), 'has_many requires a dbh');
246              
247             ## return our local copy if we have it
248             return $self->{$has_many->attr} if defined $self->{$has_many->attr};
249              
250             ## Must fetch the list.
251             my ($one_key, $many_key) = ($has_many->one_to_many->one_key, $has_many->one_to_many->many_key);
252             my $where = $many_key->mk_where(@{$self->_dbo_key_values_list});
253             my $object_list = $has_many->one_to_many->many_table_info->class->select->where($where)->selectall_arrayref($self->_dbo_dbh);
254              
255             $self->{$has_many->attr} = $object_list;
256             }
257              
258             ## The only valid value is actually
259             sub _dbo_has_many_accessor_set
260             {
261             my $self = shift;
262             my $has_many = shift;
263             my $value = shift;
264              
265             assert(UNIVERSAL::isa($has_many, 'DBomb::Meta::HasMany'),'_dbo_has_many_accessor_set requires a has_many object');
266             assert((not defined $value) && @_ == 0, 'you can only set a has_many field to undef');
267              
268             ## Delete the cached values.
269             $self->{$has_many->attr} = undef;
270             $self;
271             }
272              
273             ## Unwraps PrimaryKey objects into the column value slots
274             ## _dbo_set_primary_key($PrimaryKeyValueObj)
275             ## _dbo_set_primary_key($ColumnValueObj)
276             ## _dbo_set_primary_key([$data,...])
277             ## _dbo_set_primary_key($single_key_value)
278             sub _dbo_set_primary_key
279             {
280             my $self = shift;
281             my $pk_val = shift;
282             my $pk_info = $self->_dbo_table_info->primary_key;
283             my $pk_columns_list = $pk_info->columns_list;
284              
285             if (UNIVERSAL::isa($pk_val, 'DBomb::Value::Key')){
286              
287             my $i = 0;
288             for my $cinfo (@$pk_columns_list){
289             ## copy the values from the pk object to the corresponding columns
290             $self->_dbo_values->{$cinfo->name}->value($pk_val->value_list->[$i++]);
291             }
292             }
293             elsif (UNIVERSAL::isa($pk_val, 'DBomb::Value::Column')){ die "Not yet implemented" }
294              
295             elsif (UNIVERSAL::isa($pk_val, 'ARRAY')){
296              
297             assert(@$pk_val == @$pk_columns_list, "primary key column count must match key list count");
298             my $i = 0;
299             for my $cinfo (@$pk_columns_list){
300             ## copy the values from the pk array to the corresponding columns
301             $self->_dbo_values->{$cinfo->name}->value($pk_val->[$i++]);
302             }
303             }
304             elsif (not ref $pk_val){
305              
306             ## it's scalar
307             assert(1 == @$pk_columns_list, "new(scalar) only allowed if primary key consists of a single column");
308             my $col_name = $pk_columns_list->[0]->name;
309             $self->_dbo_values->{ $col_name }->value($pk_val);
310             }
311             else{
312             croak "unsupported primary key type";
313             }
314             }
315              
316             ## returns true if this object has a primary key value, regardless of whether that key really
317             ## exists in the database or if this object has been inserted.
318             sub _dbo_is_bound
319             {
320             my $self = shift;
321              
322             my $pk_info = $self->_dbo_table_info->primary_key;
323             my $pk_columns_list = $pk_info->columns_list;
324              
325             ## check if every pk column has a value
326             for my $pk_col (@$pk_columns_list){
327             return undef unless $self->_dbo_values->{$pk_col->name}->has_value;
328             }
329              
330             1;
331             }
332              
333             sub _dbo_fetch_columns
334             {
335             my ($self,$columns) = @_;
336             assert(UNIVERSAL::isa($columns,'ARRAY'), 'requires arrayref');
337             for(@$columns){
338             assert(UNIVERSAL::isa($_,'DBomb::Meta::ColumnInfo'), 'requires a columninfo object');
339             }
340              
341             my $query = new DBomb::GluedQuery($self->_dbo_dbh, $self, $columns);
342             $query->from($self->_dbo_table_info)
343             ->where($self->_dbo_mk_where);
344             $query->prepare;
345             $query->execute;
346             my @r = $query->fetch;
347             $query->finish;
348              
349             wantarray ? @r : $r[0];
350             }
351              
352             sub _dbo_mk_where
353             {
354             my $self = shift;
355             $self->_dbo_table_info->primary_key->mk_where(@{$self->_dbo_key_values_list})
356             }
357              
358             sub _dbo_key_values_list
359             {
360             my $self = shift;
361             assert(@_ == 0);
362             my $values = $self->_dbo_values;
363             [map {$values->{$_}->value} keys %{$self->_dbo_table_info->primary_key->columns}]
364             }
365              
366             ## Find a dbh at all costs
367             ## _dbo_dbh()
368             ## _dbo_dbh(0) ## disable die() if not found
369             sub _dbo_dbh
370             {
371             my $self = shift;
372             my $dbh;
373             my $should_croak = @_? shift : 1; ## default is to croak
374              
375             assert(@_ == 0, 'parameter count');
376              
377             ## Try through the object
378              
379             if (defined($dbh = $self->dbh)){
380             return $dbh;
381             }
382              
383             ## Try through the class
384             if (ref($self) && defined($dbh = ref($self)->dbh)){
385             return $dbh;
386             }
387              
388             ## Try through the DBomb global class
389             if (defined($dbh = DBomb->dbh)){
390             return $dbh;
391             }
392              
393             croak "Couldn't find a \$dbh to use!" if $should_croak;
394             undef
395             }
396              
397             ## TODO: this should take a group as an argument. currently, it finds the :all and expands everything!!
398             ## _dbo_expand_select_groups($columns_list)
399             sub _dbo_expand_select_groups
400             {
401             my ($class,$columns_list) = @_;
402             my $tinfo = $class->_dbo_table_info;
403              
404             assert(UNIVERSAL::isa($class,__PACKAGE__));
405             assert(UNIVERSAL::isa($columns_list,'ARRAY'));
406              
407             my %new_cols = map { $_->name => $_ } @$columns_list;
408              
409             for my $group_name (keys %{$tinfo->select_groups}){
410             next if $group_name eq ':all';
411              
412             my $group = $tinfo->select_groups->{$group_name};
413             for my $cinfo (@$columns_list){
414             next unless exists $group->{$cinfo->name};
415              
416             for my $new_cinfo (values %$group){
417             $new_cols{$new_cinfo->name} = $new_cinfo;
418             }
419             }
420             }
421             return [values %new_cols];
422             }
423              
424             ## delete()
425             sub _dbo_delete_static
426             {
427             my $class = shift;
428             assert((not ref $class), 'static delete takes a package, not an object reference');
429             return new DBomb::Query::Delete($class->_dbo_dbh)
430             ->from($class->_dbo_table_info->name);
431             }
432              
433             ## delete()
434             sub _dbo_delete
435             {
436             my $self = shift;
437             assert(ref($self) && UNIVERSAL::isa($self,__PACKAGE__));
438             assert(@_==0, 'delete takes a DBH as an argument');
439              
440             ## use the static method to create the deleter object
441             my $delete = ref($self)->_dbo_delete_static(@_);
442              
443             $delete->where($self->_dbo_mk_where);
444             $delete->prepare;
445             return $delete->execute;
446             }
447              
448             ## $class->insert()
449             ## $class->insert($dbh)
450             ## $class->insert( { col_name => value } )
451             sub _dbo_insert_static
452             {
453             my $class = shift;
454             my ($hash, $dbh);
455             my $columns = [];
456             my @args;
457              
458             while (my $a = shift){
459             if (UNIVERSAL::isa($a,'DBI::db')) { $class->dbh($a) }
460             elsif (UNIVERSAL::isa($a,'HASH')) { $hash = $a }
461             elsif (UNIVERSAL::isa($a,'ARRAY')) { push @$columns, @$a }
462             elsif (not ref $a) { push @$columns, $a }
463             else { croak "unrecognized argument to insert()" }
464             }
465              
466             ## Allow dbh to be specified later.
467             $dbh = $class->_dbo_dbh(0);
468             unshift @args, $dbh if $dbh;
469              
470             if ($hash){
471              
472             my ($values) = ([]);
473              
474             ## build list of columns and values
475             for my $cinfo (@{$class->_dbo_table_info->columns_list}){
476              
477             next if $cinfo->is_generated;
478             next if $cinfo->is_expr;
479              
480             if (exists $hash->{$cinfo->fq_name}) { push @$values, $hash->{$cinfo->fq_name}; }
481             elsif (exists $hash->{$cinfo->name}) { push @$values, $hash->{$cinfo->name}; }
482             elsif (exists $hash->{$cinfo->accessor}) { push @$values, $hash->{$cinfo->accessor}; }
483             else { next }
484              
485             push @$columns, $cinfo;
486             }
487              
488             croak "no valid columns were found in the hash!" unless @$columns;
489             push @args, $columns;
490             return new DBomb::Query::Insert(@args)->into($class->_dbo_table_info->name)
491             ->values($values);
492             }
493              
494             ## Otherwise, return a query object
495              
496             ## default columns list??
497             # my $cols = [ map { $_->name }
498             # grep { (not $_->is_generated)
499             # && (not $_->is_expr)
500             # }
501             # values %{$class->_dbo_table_info->columns}];
502             #
503             # push @args, $cols if @$cols;
504              
505             push @args, $columns;
506             return new DBomb::Query::Insert(@args)->into($class->_dbo_table_info->name);
507             }
508              
509             ## insert()
510             ## insert($dbh)
511             sub _dbo_insert
512             {
513             my $self = shift;
514              
515             $self->dbh(shift) if UNIVERSAL::isa($_[0],'DBI::db');
516             assert(defined($self->_dbo_dbh), 'insert requires a dbh');
517             assert(@_==0, 'parameter validation');
518              
519             ## TODO: allow cols to be passed in.
520             my $cols = [grep { $_->has_value
521             && (not $_->column_info->is_generated)
522             && (not $_->column_info->is_expr)
523             } @{$self->_dbo_values_list}];
524              
525             my $insert = new DBomb::Query::Insert($self->_dbo_dbh,$cols);
526              
527             $insert->prepare;
528             my $rv = $insert->execute;
529              
530             ## check for any generated primary key columns
531             for (values %{$self->_dbo_table_info->primary_key->columns}){
532             next unless $_->is_generated;
533             my $pk_val = $insert->last_insert_id;
534             $self->_dbo_values->{$_->name}->value($pk_val); ## set it
535              
536             ##$self->is_bound(1);
537             }
538              
539             return $rv;
540             }
541              
542             ## update()
543             ## update($dbh, +{ column_name => value })
544             ## values can be Expr() objects, Value objects, or whatever
545             sub _dbo_update_static
546             {
547             my $class = shift;
548             my ($hash, $dbh);
549              
550             while (local $_ = shift){
551             if (UNIVERSAL::isa($_,'DBI::db')) { $class->dbh($_) }
552             elsif (UNIVERSAL::isa($_,'HASH')) { $hash = $_ }
553             else { croak "unrecognized argument to insert()" }
554             }
555              
556             ## Allow dbh to specified later.
557             $dbh = $class->_dbo_dbh(0);
558             my @args;
559             unshift @args, $dbh if $dbh;
560              
561             my $update = new DBomb::Query::Update(@args)->table($class->_dbo_table_info->name);
562              
563             if ($hash){
564              
565             my $set_count = 0;
566              
567             ## build list of columns and values
568             for my $cinfo (@{$class->_dbo_table_info->columns_list}){
569              
570             next if $cinfo->is_generated;
571             next if $cinfo->is_expr;
572              
573             my $v;
574             if (exists $hash->{$cinfo->fq_name}) { $v = $hash->{$cinfo->fq_name}; }
575             elsif (exists $hash->{$cinfo->name}) { $v = $hash->{$cinfo->name}; }
576             elsif (exists $hash->{$cinfo->accessor}) { $v = $hash->{$cinfo->accessor}; }
577             else { next }
578              
579             $update->set($cinfo->fq_name, $v);
580             $set_count++;
581             }
582             croak "no valid columns were found in the hash!" unless $set_count > 0;
583             }
584              
585             return $update;
586             }
587              
588             ## update()
589             ## update($dbh)
590             sub _dbo_update
591             {
592             my $self = shift;
593              
594             $self->dbh(shift) if UNIVERSAL::isa($_[0],'DBI::db');
595             assert(defined($self->_dbo_dbh), 'update requires a dbh');
596              
597             my $cols = [grep {$_->has_value
598             && $_->is_modified
599             && (not $_->column_info->is_in_primary_key)
600             && (not $_->column_info->is_expr)
601             } @{$self->_dbo_values_list}];
602              
603             my $update = new DBomb::GluedUpdate($self,$self->_dbo_dbh,$cols);
604              
605             ## glue to the primary key
606             $update->where($self->_dbo_mk_where);
607             $update->prepare;
608             return $update->execute;
609             }
610              
611             ## copy_shallow()
612             ## copy_shallow($dbh)
613             ## shallow copy and return new UID
614             sub _dbo_copy_shallow
615             {
616             my $self = shift;
617             my $class = ref($self) || $self;
618              
619             $self->dbh(shift) if UNIVERSAL::isa($_[0],'DBI::db');
620             assert(defined($self->_dbo_dbh), 'update requires a dbh');
621             my $tinfo = $self->_dbo_table_info;
622              
623             ## Do a an INSERT SELECT statement and return the last insert id
624              
625             ## build list of columns and values
626             my $col_names = $self->_dbo_insertable_column_names_no_pk;
627              
628             my $inserter = $class->_dbo_insert_static;
629             $inserter->columns(@$col_names);
630             $inserter = $inserter->select(@$col_names);
631             $inserter->sql_small_result;
632             $inserter->from($tinfo->name)
633             ->where($self->_dbo_mk_where);
634              
635             $inserter->prepare($self->_dbo_dbh);
636             $inserter->execute;
637              
638             return $inserter->last_insert_id;
639             }
640              
641             ## Returns a list of column names.
642             sub _dbo_insertable_column_names_no_pk
643             {
644             my $self = shift;
645             my $class = ref($self) || $self;
646             my $col_names = [];
647             for my $cinfo (@{$class->_dbo_table_info->columns_list}){
648              
649             next if $cinfo->is_generated;
650             next if $cinfo->is_expr;
651             next if $cinfo->is_in_primary_key;
652              
653             push @$col_names, $cinfo->fq_name;
654             }
655             return $col_names;
656             }
657              
658             sub _dbo_values_list { [values %{$_[0]->_dbo_values}] }
659              
660             1;
661             __END__