File Coverage

blib/lib/DBomb/Base/Defs.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::Defs;
2              
3             =head1 NAME
4              
5             DBomb::Base::Defs - Table definition routines.
6              
7             =head1 SYNOPSIS
8              
9             package Customer;
10             use base qw(DBomb::Base);
11              
12             Customer->def_data_source ('my_db', 'Customer');
13             Customer->def_accessor ('cust_id', { column => 'id', auto_increment => 1 });
14             Customer->def_column ('id', { accessor => 'id', auto_increment => 1 }); # Same thing!
15             Customer->def_accessor ('name'};
16             Customer->def_accessor ('address');
17             Customer->def_accessor ('affiliate_id');
18             Customer->def_accessor ('database_time', { expr => 'now()'} );
19              
20              
21             ## Explicit key creation
22             Customer->def_primary_key ([qw(id cust_loc)]);
23             Customer->def_key ([qw(name affiliate_id)]);
24              
25             ## Relationship based on primary key
26             Customer->def_has_a ('affiliate', 'Affiliate', +{})
27             Customer->def_has_many ('orders', 'Order',+{})
28              
29             ## Relationship explicitly defined
30             Customer->def_has_a ('affiliate', [qw(name aff_id)], 'Affiliate_table', [qw(c_name id)]);
31             Customer->def_has_many ('orders', 'Order', ['cust_id'], ['id'],+{});
32              
33             ## Relationship based on a join
34             Employee->def_has_many ('supervisors', 'Supervisor',
35             new DBomb::Query->select('super_id')
36             ->from('emp_super')
37             ->join('employee')
38             ->using('emp_id')
39             ->where(+{emp_id => '?'}), sub { shift->emp_id });
40              
41              
42             ## select multiple columns at once for speed
43             Customer->def_select_group ([qw(name address)]);
44              
45             ## name the select_group so you can refer to it later
46             Customer->def_select_group ('reports' => [qw(name affiliate)]);
47              
48             =cut
49              
50 1     1   24548 use strict;
  1         3  
  1         77  
51 1     1   714 use warnings;
  1         3  
  1         133  
52             our $VERSION = '$Revision: 1.11 $';
53              
54 1     1   7 use Carp qw(carp croak);
  1         2  
  1         86  
55 1     1   6 use Carp::Assert;
  1         2  
  1         18  
56 1     1   858 use DBomb::Generator qw(gen_accessor);
  1         5  
  1         88  
57 1     1   206 use DBomb::Meta::TableInfo;
  0            
  0            
58             use DBomb::Meta::ColumnInfo;
59             use DBomb::Meta::HasA;
60             use DBomb::Meta::HasMany;
61             use DBomb::Meta::HasQuery;
62             use DBomb::Meta::OneToMany;
63              
64             ##
65             ## Public API subroutines
66             ##
67              
68             sub def_data_source { _dbo_def_data_source(@_) }
69             sub def_accessor { _dbo_def_accessor(@_) }
70             sub def_column { _dbo_def_column(@_) }
71             sub def_primary_key { _dbo_def_primary_key(@_) }
72             sub def_key { _dbo_def_key(@_) }
73             sub def_has_a { my @args = @_; DBomb->do_after_resolve(\&_dbo_def_has_a,\@args,[caller(0)]) }
74             sub def_has_many { my @args = @_; DBomb->do_after_resolve(\&_dbo_def_has_many,\@args,[caller(0)]) }
75             sub def_select_group { _dbo_def_select_group(@_) }
76              
77             ##
78             ## Private subroutines
79             ##
80              
81             sub _dbo_def_data_source
82             {
83             my $class = ref($_[0]) ? ref(shift) : shift;
84             my ($database,$table) = @_;
85              
86             # TODO: $database not used
87             assert(UNIVERSAL::isa($class,'DBomb::Base'), 'inherited from DBomb::Base');
88              
89             my $tinfo = $class->_dbo_table_info(DBomb->tables->{$table} = DBomb::Meta::TableInfo->factory_new($table, $class));
90             }
91              
92             sub _dbo_def_accessor
93             {
94             my $class = ref($_[0]) ? ref(shift) : shift;
95             my ($accessor, $opts) = (@_);
96             my $tinfo = $class->_dbo_table_info;
97              
98             assert(defined($tinfo), 'def_accessor requires dbo_def_data_source');
99             assert(UNIVERSAL::isa($class,'DBomb::Base'), 'inherited from DBomb::Base');
100             assert((defined($accessor) && !ref($accessor)),'valid accessor name');
101              
102             $opts ||= +{};
103             $opts->{'accessor'} = $accessor;
104             $opts->{'column'} = $accessor unless exists $opts->{'column'};
105             $opts->{'column'} = $opts->{'expr'} if exists $opts->{'expr'};
106              
107             my $cinfo = new DBomb::Meta::ColumnInfo($tinfo, $opts->{'column'}, $opts);
108              
109             ## create the accessor
110             {
111             no strict qw(refs);
112             *{ $class .'::'. $cinfo->accessor } = sub{ shift->_dbo_column_accessor($cinfo,@_) };
113             }
114             }
115              
116             ## Define a column.
117             sub _dbo_def_column
118             {
119             my $class = ref($_[0]) ? ref(shift) : shift;
120             my ($column_name, $opts) = (@_);
121             my $tinfo = $class->_dbo_table_info;
122              
123             assert(defined($tinfo), 'def_column requires dbo_def_data_source');
124             assert(UNIVERSAL::isa($class,'DBomb::Base'), 'inherited from DBomb::Base');
125             assert((defined($column_name) && !ref($column_name)),'valid column name');
126              
127             $opts ||= +{};
128             $opts->{'column'} = $column_name;
129             $opts->{'accessor'} ||= $column_name;
130             $class->_dbo_def_accessor( $opts->{'accessor'}, $opts);
131             }
132              
133             sub _dbo_def_key
134             {
135             my $class = ref($_[0]) ? ref(shift) : shift;
136             my ($columns_list, $opts) = @_;
137             my $tinfo = $class->_dbo_table_info;
138              
139             assert(defined($tinfo), 'def_key requires a data_source');
140             assert(UNIVERSAL::isa($class,'DBomb::Base'), 'inherited from DBomb::Base');
141              
142             ## replace col names with colinfo objs.
143             $columns_list = [$columns_list] unless ref $columns_list;
144             $columns_list = [map { assert((exists $tinfo->columns->{$_}),
145             "column $_ must be defined before it can be used in a key");
146             $tinfo->columns->{$_} } @$columns_list];
147              
148             ## create the key
149             my $key = new DBomb::Meta::Key($columns_list, $opts);
150              
151             return $key;
152             }
153              
154             sub _dbo_def_primary_key {
155             my $class = ref($_[0]) ? ref(shift) : shift;
156             my $pk = $class->_dbo_def_key(@_);
157              
158             ## register it with the table
159             $pk->table_info->primary_key($pk);
160             return $pk;
161             }
162              
163             ## __dbo_def_has_a ('affiliate', [qw(name aff_id)], 'Affiliate_table', [qw(c_name id)]);
164             ## _dbo_def_has_a ($accessor, $many_key, $table, $one_key, $opts)
165             sub _dbo_def_has_a
166             {
167             my $class = ref($_[0]) ? ref(shift) : shift;
168             my $accessor = shift;
169             my ($f_table, $opts, $one_to_many);
170             my $tinfo = $class->_dbo_table_info;
171              
172             assert(defined($tinfo), 'dbo_def_has_a requires dbo_def_table');
173             assert(defined($accessor), 'dbo_def_has_a requires an accessor name');
174             assert(UNIVERSAL::isa($class,'DBomb::Base'), 'inherited from DBomb::Base');
175              
176             ## Pop the opts if they exist
177             $opts = pop if UNIVERSAL::isa($_[$#$_],'HASH');
178             $opts ||= {};
179              
180             ## If there is only one arg left, assume it is the foreign table.
181             if (@_ == 1){
182             $f_table = DBomb->resolve_table_name(undef,shift);
183             assert(UNIVERSAL::isa($f_table,'DBomb::Meta::TableInfo'), 'foreign table name failed to resolve');
184             $one_to_many = $f_table->guess_one_to_many($tinfo);
185             die "Failed to guess one to many relationship from table @{[$f_table->name]} to @{[$tinfo->name]}."
186             ." Try using explicit key lists." unless defined $one_to_many;
187             }
188             else{
189             ## It's a full argument list.
190             my ($many_key, $one_key);
191             ($many_key, $f_table, $one_key) = @_;
192             $f_table = DBomb->resolve_table_name(undef,$f_table);
193             assert(UNIVERSAL::isa($f_table,'DBomb::Meta::TableInfo'), 'foreign table name failed to resolve');
194              
195             assert(defined($many_key), 'dbo_def_has_a requires a many_key');
196             assert(defined($f_table), 'dbo_def_has_a requires a foreign table');
197             assert(defined($one_key), 'dbo_def_has_a requires a one_key');
198              
199             ## Promote scalars to arrays
200             $one_key = [$one_key] if defined($one_key) && not ref $one_key;
201             $many_key = [$many_key] if defined($many_key) && not ref $many_key;
202              
203             if (UNIVERSAL::isa($many_key,'ARRAY')){
204             for (@$many_key){ assert(exists($tinfo->columns->{$_}));}
205             ## Find a matching key.
206             my $new_key = $tinfo->find_key($many_key) or die "key '@{[$tinfo->name]}(@{[join q/, /,@$many_key]})' not found. Did you forget to define a key in package @{[$tinfo->class]}?";
207             $many_key = $new_key;
208             }
209              
210             $one_key = $f_table->primary_key unless defined $one_key;
211              
212             if (UNIVERSAL::isa($one_key,'ARRAY')){
213             for (@$one_key){ assert(exists($f_table->columns->{$_}));}
214             ## Find a matching key.
215             my $new_key = $f_table->find_key($one_key) or die "key '@{[$f_table->name]}(@{[join q/, /,@$one_key]})' not found. Did you forget to define a key in package @{[$f_table->class]}?";
216             $one_key = $new_key;
217             }
218              
219              
220             ## Find or create the OneToMany object
221             ## TODO: lookup one_to_manys
222             $one_to_many = new DBomb::Meta::OneToMany($one_key,$many_key);
223             }
224              
225             my $has_a = new DBomb::Meta::HasA($one_to_many, $opts);
226              
227             ## create the accessor for the has_a object
228             {
229             no strict qw(refs);
230             *{ $class .'::'. $accessor } = sub{ shift->_dbo_has_a_accessor($has_a,@_) };
231             }
232              
233             ## Replace the accessor for each column in the has_a columns with an enhanced accessor.
234             for my $cinfo (@{$has_a->one_to_many->many_key->columns_list}){
235             no strict qw(refs);
236             no warnings 'redefine';
237             *{ $class .'::'. $cinfo->accessor } = sub { shift->_dbo_has_a_column_accessor($cinfo,@_) };
238             }
239              
240             return $has_a;
241             }
242              
243             ## _dbo_def_has_many ( $accessor, $table, [$one_columns], [$many_columns], $opts );
244             ## _dbo_def_has_many ( $accessor, $table, $opts );
245             ## _dbo_def_has_many ( $accessor, $table, $query, $bind_routine, $opts )
246             sub _dbo_def_has_many
247             {
248             my $class = ref($_[0]) ? ref(shift) : shift;
249              
250             return $class->_dbo_def_has_query(@_) if UNIVERSAL::isa($_[2],'DBomb::Query');
251              
252             my $accessor = shift;
253             my $f_table = shift;
254             my ($opts, $one_to_many);
255             my $tinfo = $class->_dbo_table_info;
256              
257             assert(defined($tinfo), 'dbo_def_has_many requires dbo_def_table');
258             assert(defined($accessor) && (not ref $accessor), 'dbo_def_has_many requires an accessor name');
259             assert(defined($f_table), 'dbo_def_has_many requires a foreign table name or info');
260             assert(UNIVERSAL::isa($class,'DBomb::Base'), 'inherited from DBomb::Base');
261              
262             $f_table = DBomb->resolve_table_name(undef, $f_table);
263             assert(UNIVERSAL::isa($f_table,'DBomb::Meta::TableInfo'), "foreign table name '$f_table' failed to resolve");
264              
265             ## Pop the opts if they exist
266             $opts = pop if UNIVERSAL::isa($_[$#$_],'HASH');
267             $opts ||= {};
268              
269             ## If there are no args left, guess the key.
270             if (@_ == 0){
271             $one_to_many = $tinfo->guess_one_to_many($f_table);
272             die "Failed to guess one to many relationship from table @{[$tinfo->name]} to @{[$f_table->name]}."
273             ." Try using explicit key lists." unless defined $one_to_many;
274             }
275             else{
276             assert(@_ == 2, "number of arguments to def_has_many");
277             my ($one_key, $many_key) = @_;
278              
279             assert(defined($one_key), 'dbo_def_has_many requires a one_key');
280             assert(defined($many_key), 'dbo_def_has_many requires a many_key');
281              
282             ## Promote scalars to arrays
283             $one_key = [$one_key] if defined($one_key) && not ref $one_key;
284             $many_key = [$many_key] if defined($many_key) && not ref $many_key;
285              
286             if (UNIVERSAL::isa($many_key,'ARRAY')){
287             for (@$many_key){ assert(exists($f_table->columns->{$_}), "column $_ must exist");}
288             ## Find a matching key.
289             my $new_key = $f_table->find_key($many_key) or die "key '@{[$f_table->name]}(@{[join q/, /,@$many_key]})' not found. Did you forget to define a key in package @{[$f_table->class]}?";
290             $many_key = $new_key;
291             }
292              
293             if (UNIVERSAL::isa($one_key,'ARRAY')){
294             for (@$one_key){ assert(exists($tinfo->columns->{$_}), "column $_ must exist");}
295             ## Find a matching key.
296             my $new_key = $tinfo->find_key($one_key) or die "key '@{[$tinfo->name]}(@{[join q/, /,@$one_key]})' not found. Did you forget to define a key in package @{[$tinfo->class]}?";
297             $one_key = $new_key;
298             }
299              
300             ## Find or create the OneToMany object
301             ## TODO: lookup one_to_manys
302             $one_to_many = new DBomb::Meta::OneToMany($one_key,$many_key);
303             }
304              
305             my $has_many = new DBomb::Meta::HasMany($one_to_many, $opts);
306              
307             ## create the accessor for the has_many object
308             {
309             no strict qw(refs);
310             *{ $class .'::'. $accessor } = sub{ shift->_dbo_has_many_accessor($has_many, @_) };
311             }
312              
313             return $has_many;
314             }
315              
316             ## _dbo_def_has_query ( $accessor, $f_table, $query, sub{return $bind_value},..., $opts )
317             sub _dbo_def_has_query
318             {
319             my $class = ref($_[0]) ? ref(shift) : shift;
320             my $accessor = shift;
321             my $f_table = shift;
322             my $query = shift;
323             my $opts;
324              
325             my $tinfo = $class->_dbo_table_info;
326             $opts = pop if UNIVERSAL::isa($_[$#$_],'HASH');
327             $opts ||= {};
328             my @bind_subs = @_;
329              
330             assert(defined($tinfo), 'dbo_def_has_many requires dbo_def_table');
331             assert(defined($accessor) && (not ref $accessor), 'dbo_def_has_many requires an accessor name');
332             assert(UNIVERSAL::isa($class,'DBomb::Base'), 'inherited from DBomb::Base');
333             for (@bind_subs){
334             assert(UNIVERSAL::isa($_,'CODE'), 'bind value subroutine(s) must be code refs');
335             }
336              
337             my $has_query = new DBomb::Meta::HasQuery($tinfo, $f_table, $query, [@bind_subs], $opts);
338              
339             $has_query->resolve;
340             assert(UNIVERSAL::isa($has_query->f_table,'DBomb::Meta::TableInfo'), "f_table name '$f_table' failed to resolve");
341              
342             ## Register with table_info
343             push @{$tinfo->has_queries}, $has_query;
344              
345             ## create the accessor for the has_many object
346             {
347             no strict qw(refs);
348             *{ $class .'::'. $accessor } = sub{ shift->_dbo_has_query_accessor($has_query, @_) };
349             }
350              
351             return $has_query;
352             }
353              
354             ## convert column_names to column_info
355             ## _dbo_promote_columns ([ $column,.. ])
356             sub _dbo_promote_columns
357             {
358             my ($class, $columns_list) = @_;
359             assert(UNIVERSAL::isa($class,__PACKAGE__));
360             assert(UNIVERSAL::isa($columns_list,'ARRAY'));
361             my $a = [];
362             for my $c (@$columns_list) {
363             unless (UNIVERSAL::isa($c,'DBomb::Meta::ColumnInfo')){
364             $c = $class->_dbo_table_info->columns->{$c};
365             assert(defined($c), "column '$c' must exist in table @{[$class->_dbo_table_info->name]}");
366             }
367             push @$a, $c;
368             }
369             $a
370             }
371              
372              
373             ## _dbo_def_select_group ([ $cols ])
374             ## _dbo_def_select_group ( $group => [ $cols...] )
375             sub _dbo_def_select_group
376             {
377             my $class = shift;
378             my $columns_list = pop;
379             my $group;
380             $group = shift if @_;
381              
382             assert(UNIVERSAL::isa($class,__PACKAGE__));
383             assert(@_ == 0, 'paramter count to def_select_group');
384             assert((not defined $group)||(not ref $group), 'group name must be a string');
385             assert(UNIVERSAL::isa($columns_list,'ARRAY'), 'expected a listref of columns');
386              
387             $columns_list = $class->_dbo_promote_columns($columns_list);
388             $class->_dbo_table_info->add_select_group($group, $columns_list);
389             }
390              
391             1;
392             __END__