File Coverage

blib/lib/Fey/Table.pm
Criterion Covered Total %
statement 11 13 84.6
branch n/a
condition n/a
subroutine 5 5 100.0
pod n/a
total 16 18 88.8


line stmt bran cond sub pod time code
1             package Fey::Table;
2             BEGIN {
3 1     1   28453 $Fey::Table::VERSION = '0.40';
4             }
5              
6 1     1   11 use strict;
  1         2  
  1         45  
7 1     1   5 use warnings;
  1         2  
  1         29  
8 1     1   859 use namespace::autoclean;
  1         31793  
  1         8  
9              
10 1     1   653 use Fey::Column;
  0            
  0            
11             use Fey::Exceptions qw( param_error );
12             use Fey::NamedObjectSet;
13             use Fey::Schema;
14             use Fey::Table::Alias;
15             use Fey::Types qw(
16             ArrayRef Bool HashRef Str Undef Column ColumnOrName NamedObjectSet Schema
17             );
18             use List::AllUtils qw( any all first_index );
19             use Scalar::Util qw( blessed weaken );
20              
21             use Moose;
22             use MooseX::Params::Validate qw( pos_validated_list );
23             use MooseX::SemiAffordanceAccessor;
24             use MooseX::StrictConstructor;
25             use Moose::Util::TypeConstraints;
26              
27             with 'Fey::Role::TableLike';
28              
29             with 'Fey::Role::MakesAliasObjects' => {
30             self_param => 'table',
31             alias_class => 'Fey::Table::Alias',
32             };
33              
34             has 'id' => (
35             is => 'ro',
36             lazy_build => 1,
37             init_arg => undef,
38             );
39              
40             has 'name' => (
41             is => 'ro',
42             isa => Str,
43             required => 1,
44             );
45              
46             has 'is_view' => (
47             is => 'ro',
48             isa => Bool,
49             default => 0,
50             );
51              
52             has '_keys' => (
53             traits => ['Array'],
54             is => 'bare',
55             isa => ArrayRef[NamedObjectSet],
56             default => sub { [] },
57             handles => {
58             _keys => 'elements',
59             _add_key => 'push',
60             _delete_key => 'splice',
61             },
62              
63             );
64              
65             has '_columns' => (
66             is => 'ro',
67             isa => NamedObjectSet,
68             default => sub { return Fey::NamedObjectSet->new() },
69             handles => {
70             columns => 'objects',
71             column => 'object',
72             },
73             );
74              
75             has 'schema' => (
76             is => 'rw',
77             isa => Undef | Schema,
78             weak_ref => 1,
79             writer => '_set_schema',
80             clearer => '_clear_schema',
81             predicate => 'has_schema',
82             );
83              
84             has 'candidate_keys' => (
85             is => 'ro',
86             isa => ArrayRef[ArrayRef[Column]],
87             clearer => '_clear_candidate_keys',
88             lazy_build => 1,
89             init_arg => undef,
90             );
91              
92             after '_add_key', '_delete_key' => sub { $_[0]->_clear_candidate_keys() };
93              
94             has 'primary_key' => (
95             is => 'ro',
96             isa => ArrayRef[Column],
97             clearer => '_clear_primary_key',
98             lazy_build => 1,
99             init_arg => undef,
100             );
101              
102             after '_clear_candidate_keys' => sub { $_[0]->_clear_primary_key() };
103              
104             has '_aliased_tables' => (
105             traits => ['Hash'],
106             is => 'bare',
107             isa => HashRef,
108             lazy => 1,
109             default => sub { {} },
110             handles => {
111             _aliased_table => 'get',
112             _store_aliased_table => 'set',
113             _has_aliased_table => 'exists',
114             },
115             );
116              
117             with 'Fey::Role::Named';
118              
119             sub add_column {
120             my $self = shift;
121             my ($col) = pos_validated_list( \@_, { isa => Column } );
122              
123             my $name = $col->name();
124             param_error "The table already has a column named $name."
125             if $self->column($name);
126              
127             $self->_columns()->add($col);
128              
129             $col->_set_table($self);
130              
131             return $self;
132             }
133              
134             sub remove_column {
135             my $self = shift;
136             my ($col)
137             = pos_validated_list( \@_, { isa => ColumnOrName } );
138              
139             $col = $self->column($col)
140             unless blessed $col;
141              
142             if ( my $schema = $self->schema() ) {
143             for my $fk ( grep { $_->has_column($col) }
144             $schema->foreign_keys_for_table($self) ) {
145             $schema->remove_foreign_key($fk);
146             }
147             }
148              
149             my $name = $col->name();
150              
151             for my $k ( $self->_keys() ) {
152             $self->remove_candidate_key( $k->objects() )
153             if $k->object($name);
154             }
155              
156             $self->_columns()->delete($col);
157              
158             $col->_clear_table();
159              
160             return $self;
161             }
162              
163             sub _build_candidate_keys {
164             my $self = shift;
165              
166             return [ map { [ $_->objects() ] } $self->_keys() ];
167             }
168              
169             sub _build_primary_key {
170             my $self = shift;
171              
172             my $keys = $self->candidate_keys();
173              
174             return $keys->[0] || [];
175             }
176              
177             sub add_candidate_key {
178             my $self = shift;
179              
180             my $count = @_ ? @_ : 1;
181             my (@cols) = pos_validated_list(
182             \@_,
183             ( ( { isa => ColumnOrName } ) x $count ),
184             MX_PARAMS_VALIDATE_NO_CACHE => 1,
185             );
186              
187             for my $name ( map { blessed $_ ? $_->name() : $_ } @cols ) {
188             param_error "The column $name is not part of the "
189             . $self->name()
190             . ' table.'
191             unless $self->column($name);
192             }
193              
194             $_ = $self->column($_) for grep { !blessed $_ } @cols;
195              
196             return if $self->has_candidate_key(@cols);
197              
198             $self->_add_key( Fey::NamedObjectSet->new(@cols) );
199              
200             return;
201             }
202              
203             sub remove_candidate_key {
204             my $self = shift;
205              
206             my $count = @_ ? @_ : 1;
207             my (@cols) = pos_validated_list(
208             \@_,
209             ( ( { isa => ColumnOrName } ) x $count ),
210             MX_PARAMS_VALIDATE_NO_CACHE => 1,
211             );
212              
213             for my $name ( map { blessed $_ ? $_->name() : $_ } @cols ) {
214             param_error "The column $name is not part of the "
215             . $self->name()
216             . ' table.'
217             unless $self->column($name);
218             }
219              
220             $_ = $self->column($_) for grep { !blessed $_ } @cols;
221              
222             my $set = Fey::NamedObjectSet->new(@cols);
223              
224             my $idx = first_index { $_->is_same_as($set) } $self->_keys();
225              
226             $self->_delete_key( $idx, 1 )
227             if $idx >= 0;
228              
229             return;
230             }
231              
232             sub has_candidate_key {
233             my $self = shift;
234              
235             my $count = @_ ? @_ : 1;
236             my (@cols) = pos_validated_list(
237             \@_,
238             ( ( { isa => ColumnOrName } ) x $count ),
239             MX_PARAMS_VALIDATE_NO_CACHE => 1,
240             );
241              
242             for my $name ( map { blessed $_ ? $_->name() : $_ } @cols ) {
243             param_error "The column $name is not part of the "
244             . $self->name()
245             . ' table.'
246             unless $self->column($name);
247             }
248              
249             $_ = $self->column($_) for grep { !blessed $_ } @cols;
250              
251             my $set = Fey::NamedObjectSet->new(@cols);
252              
253             return 1
254             if any { $_->is_same_as($set) } $self->_keys();
255              
256             return 0;
257             }
258              
259             # Caching the objects by name prevents a weird bug where we have two
260             # aliases of the same name, and one disappears because of weak
261             # references, causing weird errors.
262             around 'alias' => sub {
263             my $orig = shift;
264             my $self = shift;
265              
266             # bleh, duplicating code from Aliasable
267             my %p = @_ == 1 ? ( alias_name => $_[0] ) : @_;
268              
269             if ( defined $p{alias_name} ) {
270             return $self->_aliased_table( $p{alias_name} )
271             if $self->_has_aliased_table( $p{alias_name} );
272             }
273              
274             my $alias = $orig->( $self, %p );
275              
276             $self->_store_aliased_table( $alias->alias_name() => $alias );
277              
278             return $alias;
279             };
280              
281             sub is_alias {0}
282              
283             sub aliased_column {
284             my $self = shift;
285             my $prefix = shift;
286             my $name = shift;
287              
288             my $col = $self->column($name)
289             or return;
290              
291             return $col->alias( alias_name => $prefix . $col->name() );
292             }
293              
294             sub aliased_columns {
295             my $self = shift;
296             my $prefix = shift;
297              
298             my @names = @_ ? @_ : map { $_->name() } $self->columns();
299              
300             return map { $self->aliased_column( $prefix, $_ ) } @names;
301             }
302              
303             sub sql {
304             return $_[1]->quote_identifier( $_[0]->name() );
305             }
306              
307             sub sql_with_alias { goto &sql }
308              
309             sub _build_id { $_[0]->name() }
310              
311             __PACKAGE__->meta()->make_immutable();
312              
313             1;
314              
315             # ABSTRACT: Represents a table (or view)
316              
317              
318              
319             =pod
320              
321             =head1 NAME
322              
323             Fey::Table - Represents a table (or view)
324              
325             =head1 VERSION
326              
327             version 0.40
328              
329             =head1 SYNOPSIS
330              
331             my $table = Fey::Table->new( name => 'User' );
332              
333             =head1 DESCRIPTION
334              
335             This class represents a table or view in a schema. From the standpoint
336             of SQL construction in Fey, a table and a view are basically the same
337             thing.
338              
339             =head1 METHODS
340              
341             This class provides the following methods:
342              
343             =head2 Fey::Table->new()
344              
345             my $table = Fey::Table->new( name => 'User' );
346              
347             my $table = Fey::Table->new( name => 'ActiveUser',
348             is_view => 1,
349             );
350              
351             This method constructs a new C<Fey::Table> object. It takes the
352             following parameters:
353              
354             =over 4
355              
356             =item * name - required
357              
358             The name of the table.
359              
360             =item * is_view - defaults to 0
361              
362             A boolean indicating whether this table is a view.
363              
364             =back
365              
366             =head2 $table->name()
367              
368             Returns the name of the table.
369              
370             =head2 $table->is_view()
371              
372             Returns a boolean indicating whether the object is a view.
373              
374             =head2 $table->schema()
375              
376             Returns the C<Fey::Schema> object that this table belongs to. This is
377             set when the table is added to a schema via the C<<
378             Fey::Schema->add_table() >> method.
379              
380             =head2 $table->add_column($column)
381              
382             This adds a new column to the schema. The column must be a
383             C<Fey::Column> object. Adding the column to the table sets the table
384             for the column, so that C<< $column->table() >> returns the correct
385             object.
386              
387             If the table already has a column with the same name, an exception is
388             thrown.
389              
390             =head2 $table->remove_column($column)
391              
392             Remove the specified column from the table. If the column was part of
393             any foreign keys, these are removed from the schema. If this column is
394             part of any keys for the table, those keys will be removed. Removing
395             the column unsets the table for the column.
396              
397             The table can be specified either by name or by passing in a
398             C<Fey::Column> object.
399              
400             =head2 $table->column($name)
401              
402             Given a column name, this method returns the matching column object,
403             if one exists.
404              
405             =head2 $table->columns
406              
407             =head2 $table->columns(@names)
408              
409             When this method is called with no arguments, it returns all of the columns in
410             the table. Columns are returned in the order with which they were added to the
411             table.
412              
413             If given a list of names, it returns only the specified columns. If a name is
414             given which doesn't match a column in the table, then it is ignored.
415              
416             =head2 $table->candidate_keys()
417              
418             Returns all of the candidate keys for the table as an array
419             reference. Each element of the reference is in turn an array reference
420             containing one or more columns.
421              
422             =head2 $table->has_candidate_key(@columns)
423              
424             This method returns true if the table has the given key. A key is
425             identified as a list of names or C<Fey::Column> objects.
426              
427             =head2 $table->add_candidate_key(@columns)
428              
429             This method adds a new candidate key to the table. The list of columns
430             can contain either names or C<Fey::Column> objects.
431              
432             A candidate key is one or more columns which uniquely identify a row
433             in that table.
434              
435             If a name or column is specified which doesn't belong to the table, an
436             exception will be thrown.
437              
438             =head2 $table->remove_candidate_key(@columns)
439              
440             This method removes a candidate key for the table. The list of columns
441             can contain either names or C<Fey::Column> objects.
442              
443             If a name or column is specified which doesn't belong to the table, an
444             exception will be thrown.
445              
446             =head2 $table->primary_key()
447              
448             This is a convenience method that simply returns the first candidate
449             key added to the table. The key is returned as an array reference of
450             column objects.
451              
452             =head2 $table->alias(%p)
453              
454             =head2 $table->alias($alias_name)
455              
456             This method returns a new C<Fey::Table::Alias> object based on the
457             table. Any parameters passed to this method will be passed through to
458             C<< Fey::Table::Alias->new() >>.
459              
460             As a shortcut, if you pass a single argument to this method, it will
461             be passed as the "alias_name" parameter to C<<
462             Fey::Table::Alias->new() >>.
463              
464             =head2 $table->is_alias()
465              
466             Always returns false.
467              
468             =head2 $table->aliased_column( $prefix, $column_name )
469              
470             This method returns a new L<Fey::Column::Alias> object. The alias's
471             name is generated by concatenating the specified prefix and the
472             column's real name.
473              
474             =head2 $table->aliased_columns( $prefix, @column_names )
475              
476             This method returns a list of new L<Fey::Column::Alias> objects. The
477             alias names are generated by concatenating the specified prefix and
478             the column's real name.
479              
480             If you omit the list of column names, it returns aliases for I<all> of the
481             columns in table, in same order as returned by C<< $table->columns() >>.
482              
483             =head2 $table->sql()
484              
485             =head2 $table->sql_with_alias()
486              
487             Returns the appropriate SQL snippet for the table.
488              
489             =head2 $table->id()
490              
491             Returns a unique identifier for the table.
492              
493             =head1 ROLES
494              
495             This class does the L<Fey::Role::TableLike>, L<Fey::Role::MakesAliasObjects>,
496             and L<Fey::Role::Named> roles.
497              
498             =head1 BUGS
499              
500             See L<Fey> for details on how to report bugs.
501              
502             =head1 AUTHOR
503              
504             Dave Rolsky <autarch@urth.org>
505              
506             =head1 COPYRIGHT AND LICENSE
507              
508             This software is Copyright (c) 2011 by Dave Rolsky.
509              
510             This is free software, licensed under:
511              
512             The Artistic License 2.0 (GPL Compatible)
513              
514             =cut
515              
516              
517             __END__
518