File Coverage

blib/lib/Class/DBI/FormBuilder/Meta/Table.pm
Criterion Covered Total %
statement 18 110 16.3
branch 0 32 0.0
condition 0 23 0.0
subroutine 6 17 35.2
pod 5 5 100.0
total 29 187 15.5


line stmt bran cond sub pod time code
1             package Class::DBI::FormBuilder::Meta::Table;
2 31     31   240 use strict;
  31         64  
  31         1407  
3 31     31   185 use warnings;
  31         69  
  31         1062  
4 31     31   176 use Carp();
  31         380  
  31         514  
5              
6 31     31   216 use List::Util();
  31         54  
  31         2597  
7              
8 31     31   31488 use Class::DBI::FormBuilder::Meta::Column;
  31         97  
  31         323  
9              
10 31     31   3814 use base qw( Class::Accessor );
  31         67  
  31         67536  
11              
12             __PACKAGE__->mk_accessors( qw( dbh cdbi_class _columns_hash catalog schema ) );
13              
14             =head1 NAME
15              
16             Class::DBI::FormBuilder::Meta::Table
17              
18             =head1 DESCRIPTION
19              
20             Access to column metadata.
21              
22             =head1 METHODS
23              
24             =over 4
25              
26             =item instance( $cdbi, %args )
27              
28             Returns an instance for the C<$cdbi> class (C<$cdbi> can be a class name or object).
29              
30             The C<%args> hash is optional. Keys can be C and C, which are also
31             available as accessors. Both default to C.
32              
33             =item catalog
34              
35             Get/set the catalog.
36              
37             =item schema
38              
39             Get/set the schema.
40              
41             =item dbh
42              
43             Get/set the DBI database handle (you probably don't want to set it).
44              
45             =item cdbi_class
46              
47             Get/set the CDBI class (you probably don't want to set it).
48              
49             =cut
50              
51             {
52             # per-process instances, keyed by CDBI class
53             my %Instances;
54            
55             # must be a singleton, loading meta is a fairly expensive operation (the queries can return
56             # lots of data)
57             sub instance
58             {
59 0     0 1   my ( $proto, $cdbi, %args ) = @_;
60            
61 0 0 0       $cdbi && UNIVERSAL::isa( $cdbi, 'Class::DBI' ) or
62             Carp::croak( "Must supply a CDBI class or object (got '$cdbi')" );
63            
64 0   0       my $cdbi_class = ref $cdbi || $cdbi;
65            
66 0 0         return $Instances{$cdbi_class} if $Instances{$cdbi_class};
67            
68             # first time - build a new object
69            
70 0   0       my $self = bless { _columns_hash => {} }, ref $proto || $proto;
71            
72 0           $self->cdbi_class($cdbi_class);
73            
74 0           $self->dbh( $cdbi_class->db_Main );
75            
76 0   0       $self->catalog( $args{catalog} || undef );
77            
78 0   0       $self->schema( $args{schema} || undef );
79            
80 0           $self->_load_meta;
81            
82 0           $Instances{$cdbi_class} = $self;
83            
84 0           return $self;
85             }
86             }
87              
88             sub _load_meta
89             {
90 0     0     my ( $self ) = @_;
91            
92 0 0         die "Meta already loaded" if keys %{ $self->_columns_hash };
  0            
93            
94 0           my $dbh = $self->dbh;
95 0           my $table = $self->cdbi_class->table;
96            
97             #$dbh->{FetchHashKeyName} = 'NAME_lc';
98            
99             # undef does not constrain the data returned for that key
100             # I'm suspicious that setting catalog and schema to undef might break RDBMSs that actually
101             # do supply that information.
102            
103             # '%' is a search pattern for columns - matches all columns
104 0 0         if ( my $sth = $dbh->column_info( $self->catalog, $self->schema, $table, '%' ) )
105             {
106 0 0         $dbh->errstr && die "Error getting column info sth: " . $dbh->errstr;
107 0           $self->_load_type_meta( $sth );
108             }
109             else
110             {
111 0           $self->_load_typeless_meta;
112             }
113             }
114              
115             # typeless db e.g. sqlite
116             sub _load_typeless_meta
117             {
118 0     0     my ( $self ) = @_;
119              
120 0 0         $self->cdbi_class->set_sql( fb_meta_dummy => 'SELECT * FROM __TABLE__ WHERE 1=0' )
121             unless $self->cdbi_class->can( 'sql_fb_meta_dummy' );
122              
123 0           my $sth = $self->cdbi_class->sql_fb_meta_dummy;
124            
125 0 0         $sth->execute or die "Error executing column info: " . $sth->errstr;;
126            
127             # see 'Statement Handle Attributes' in the DBI docs for a list of available attributes
128 0           my $cols = $sth->{NAME};
129 0           my $types = $sth->{TYPE};
130             # my $sizes = $sth->{PRECISION}; # empty
131             # my $nulls = $sth->{NULLABLE}; # empty
132            
133             # we haven't actually fetched anything from the sth, so need to tell DBI we're not going to
134 0           $sth->finish;
135            
136 0           my $order = 0;
137            
138 0           foreach my $col ( @$cols )
139             {
140 0           my $meta;
141            
142 0           $meta->{nullable} = 1;
143 0           $meta->{is_nullable} = 'yes';
144            
145             # in my limited testing, the columns are returned in the same order as they were defined in the schema
146 0           $meta->{ordinal_position} = $order++;
147            
148             # type_name is taken literally from the schema, but is not actually used by sqlite,
149             # so it can be anything, e.g. varchar or varchar(xxx) or VARCHAR etc.
150 0           $meta->{type_name} = _fixup_type( shift( @$types ) );
151            
152 0           $self->_add_column( $col, $meta );
153             }
154             }
155              
156             # $type may be something like varchar(255) from sqlite
157             sub _fixup_type
158             {
159 0     0     my ( $type ) = @_;
160 0           $type =~ s/\(.+$//;
161 0           return $type;
162             }
163              
164             sub _load_type_meta
165             {
166 0     0     my ($self, $sth) = @_;
167            
168 0           while ( my $row = $sth->fetchrow_hashref )
169             {
170 0           my ($meta, $col_name);
171            
172 0           foreach my $key ( @{ Class::DBI::FormBuilder::Meta::Column->column_attributes } )
  0            
173             {
174 0   0       my $value = $row->{$key} || $row->{ uc $key };
175 0           $meta->{$key} = $value;
176 0   0       $col_name = $row->{COLUMN_NAME} || $row->{column_name};
177             }
178            
179 0           $self->_add_column($col_name, $meta);
180             }
181             }
182              
183             sub _add_column
184             {
185 0     0     my ( $self, $name, $meta ) = @_;
186            
187 0           $self->_columns_hash->{$name} = Class::DBI::FormBuilder::Meta::Column->new($self, $name, $meta);
188             }
189              
190             =item column_deep_type( $field )
191              
192             Returns the type of the field. If C<$field> refers to a relationship (e.g. C or
193             C), returns the type of the column in the related table.
194              
195             =cut
196              
197             # $col might be a related (has_many or might_have) accessor - i.e. it refers to a column in
198             # another table, in which case, the type of the column in that table is returned
199             sub column_deep_type
200             {
201 0     0 1   my ($self, $field) = @_;
202            
203 0 0         Carp::croak "Must supply a column name - got a ref - '$field' " . ref($field) if ref $field;
204            
205 0           my $them = $self->cdbi_class;
206            
207 0           my $column = $self->column($field);
208            
209 0 0         return $column->type if $column;
210            
211             # no such column - must be a related accessor
212            
213 0           my ($other, $rel_type) = $self->related_class_and_rel_type($field);
214            
215 0 0         Carp::croak "Non-existent column '$field' in '$them' is not related to anything" unless $other;
216            
217 0           my $meta = $them->meta_info($rel_type, $field);
218            
219 0           my $fk = $meta->{args}->{foreign_key};
220            
221 0           my $other_meta = $self->instance($other);
222            
223 0 0         my $type = $other_meta->column($fk)->type if $fk;
224              
225 0 0         die "No type detected for column '$field' in '$them' or column '$fk' in '$other'" unless $type;
226            
227 0           return $type;
228             }
229              
230             =item related_class_and_rel_type( $field )
231              
232             =cut
233              
234             sub related_class_and_rel_type
235             {
236 0     0 1   my ( $self, $field ) = @_;
237            
238 0           my $them = $self->cdbi_class;
239            
240 0           my @rel_types = keys %{ $them->meta_info };
  0            
241              
242 0     0     my $related_meta = List::Util::first { $_ } map { $them->meta_info( $_ => $field ) } @rel_types;
  0            
  0            
243            
244 0 0         return unless $related_meta;
245              
246 0           my $rel_type = $related_meta->name;
247            
248 0   0       my $mapping = $related_meta->{args}->{mapping} || [];
249            
250 0           my $related_class;
251            
252 0 0         if ( @$mapping )
253             {
254             #use Data::Dumper;
255             #my $foreign_meta = $related_meta->foreign_class->meta_info( 'has_a' );
256             #die Dumper( [ $mapping, $rel_type, $related_meta, $foreign_meta ] );
257 0           $related_class = $related_meta->foreign_class
258             ->meta_info( 'has_a' )
259             ->{ $$mapping[0] }
260             ->foreign_class;
261            
262             #my $accessor = $related_meta->accessor;
263             #my $map = $$mapping[0];
264             }
265             else
266             {
267 0           $related_class = $related_meta->foreign_class;
268             }
269            
270 0           return ($related_class, $rel_type);
271             }
272              
273             =item column( $col_name )
274              
275             If C<$col_name> is a column in this class, returns a L
276             object for that column. Otherwise, returns C.
277              
278             =cut
279              
280             # returns a CDBI::FB::Meta::Column object or undef - e.g. if asked for a has_many field
281             # note: column_deep_type relies on the undef for related columns
282             sub column
283             {
284 0     0 1   my ($self, $col_name) = @_;
285            
286 0           my $h = $self->_columns_hash;
287            
288 0 0         Carp::croak "meta not loaded" unless $h;
289            
290 0           return $h->{ $col_name };
291             }
292              
293             =item columns()
294              
295             Returns L objects, in the same order as defined in the database.
296              
297             =back
298              
299             =cut
300              
301             sub columns
302             {
303 0     0 1   my ( $self, $group ) = @_;
304              
305 0   0       $group ||= 'All';
306              
307 0           my @columns = $self->cdbi_class->columns( $group );
308            
309 0           my @ordered = map { $_->[0] }
  0            
310 0 0         sort { $a->[1] <=> $b->[1] }
311 0           grep { Carp::croak "Bad column " . $_->[0] . " has order: " . $_->[1] unless defined $_->[1]; $_ }
  0            
312 0           map { [ $_, $self->column( $_->name )->order ] }
313             @columns;
314            
315 0           return @ordered;
316             }
317              
318             1;
319              
320              
321             __END__