1package Alzabo::Table;
2
3use strict;
4use vars qw($VERSION);
5
6use Alzabo;
7
8use Params::Validate qw( :all );
9Params::Validate::validation_options( on_fail => sub { Alzabo::Exception::Params->throw( error => join '', @_ ) } );
10
11use Tie::IxHash;
12
13$VERSION = 2.0;
14
151;
16
17sub schema
18{
19    my $self = shift;
20
21    return $self->{schema};
22}
23
24sub name
25{
26    my $self = shift;
27
28    return $self->{name};
29}
30
31use constant HAS_COLUMN_SPEC => { type => SCALAR };
32
33sub has_column
34{
35    my $self = shift;
36
37    validate_pos( @_, HAS_COLUMN_SPEC );
38
39    return $self->{columns}->FETCH(shift);
40}
41
42sub column
43{
44    my $self = shift;
45    my $name = shift;
46
47    if ( my $col = $self->{columns}->FETCH($name) )
48    {
49        return $col;
50    }
51    else
52    {
53        Alzabo::Exception::Params->throw
54            ( error => "Column $name doesn't exist in $self->{name}" );
55    }
56}
57
58sub columns
59{
60    my $self = shift;
61
62    return $self->column(@_) if @_ ==1 ;
63    return map { $self->column($_) } @_ if @_ > 1;
64    return $self->{columns}->Values;
65}
66
67sub primary_key
68{
69    my $self = shift;
70
71    return unless @{ $self->{pk} };
72
73    return ( wantarray ?
74             $self->{columns}->Values( @{ $self->{pk} } ) :
75             $self->{columns}->Values( $self->{pk}[0] )
76           );
77}
78
79sub primary_key_size
80{
81    my $self = shift;
82
83    return scalar @{ $self->{pk} };
84}
85
86use constant COLUMN_IS_PRIMARY_KEY_SPEC => { isa => 'Alzabo::Column' };
87
88sub column_is_primary_key
89{
90    my $self = shift;
91
92    validate_pos( @_, COLUMN_IS_PRIMARY_KEY_SPEC );
93
94    my $name = shift->name;
95
96    Alzabo::Exception::Params->throw( error => "Column $name doesn't exist in $self->{name}" )
97        unless $self->{columns}->EXISTS($name);
98
99    my $idx = $self->{columns}->Indices($name);
100    return 1 if grep { $idx == $_ } @{ $self->{pk} };
101
102    return 0;
103}
104
105sub attributes
106{
107    return keys %{ $_[0]->{attributes} };
108}
109
110use constant HAS_ATTRIBUTE_SPEC => { attribute => { type => SCALAR },
111                                     case_sensitive => { type => SCALAR,
112                                                         default => 0 },
113                                   };
114
115sub has_attribute
116{
117    my $self = shift;
118    my %p = validate( @_, HAS_ATTRIBUTE_SPEC );
119
120    if ( $p{case_sensitive} )
121    {
122        return exists $self->{attributes}{ $p{attribute} };
123    }
124    else
125    {
126        return 1 if grep { lc $p{attribute} eq lc $_ } keys %{ $self->{attributes} };
127    }
128}
129
130use constant FOREIGN_KEYS_SPEC => { column => { isa => 'Alzabo::Column' },
131                                    table  => { isa => 'Alzabo::Table' },
132                                  };
133
134sub foreign_keys
135{
136    my $self = shift;
137
138    validate( @_, FOREIGN_KEYS_SPEC );
139    my %p = @_;
140
141    my $c_name = $p{column}->name;
142    my $t_name = $p{table}->name;
143
144    Alzabo::Exception::Params->throw( error => "Column $c_name doesn't exist in $self->{name}" )
145        unless $self->{columns}->EXISTS($c_name);
146
147    Alzabo::Exception::Params->throw( error => "No foreign keys to $t_name exist in $self->{name}" )
148        unless exists $self->{fk}{$t_name};
149
150    Alzabo::Exception::Params->throw( error => "Column $c_name is not a foreign key to $t_name in $self->{name}" )
151        unless exists $self->{fk}{$t_name}{$c_name};
152
153    return wantarray ? @{ $self->{fk}{$t_name}{$c_name} } : $self->{fk}{$t_name}{$c_name}[0];
154}
155
156use constant FOREIGN_KEYS_BY_TABLE_SPEC => { isa => 'Alzabo::Table' };
157
158sub foreign_keys_by_table
159{
160    my $self = shift;
161
162    validate_pos( @_, FOREIGN_KEYS_BY_TABLE_SPEC );
163    my $name = shift->name;
164
165    my $fk = $self->{fk};
166
167    my %fk;
168    if ( exists $fk->{$name} )
169    {
170        foreach my $c ( keys %{ $fk->{$name} } )
171        {
172            return $fk->{$name}{$c}[0] unless wantarray;
173
174            $fk{$_} = $_ for @{ $fk->{$name}{$c} };
175        }
176    }
177
178    return values %fk;
179}
180
181use constant FOREIGN_KEYS_BY_COLUMN_SPEC => { isa => 'Alzabo::Column' };
182
183sub foreign_keys_by_column
184{
185    my $self = shift;
186
187    my ($col) = validate_pos( @_, FOREIGN_KEYS_BY_COLUMN_SPEC );
188
189    Alzabo::Exception::Params->throw( error => "Column " . $col->name . " doesn't exist in $self->{name}" )
190        unless $self->{columns}->EXISTS( $col->name );
191
192    my $fk = $self->{fk};
193
194    my %fk;
195    foreach my $t (keys %$fk)
196    {
197        if ( exists $fk->{$t}{ $col->name } )
198        {
199            return $fk->{$t}{ $col->name }[0] unless wantarray;
200
201            $fk{$_} = $_ for @{ $fk->{$t}{ $col->name } };
202        }
203    }
204
205    return values %fk;
206}
207
208sub all_foreign_keys
209{
210    my $self = shift;
211
212    my %seen;
213    my @fk;
214    my $fk = $self->{fk};
215    foreach my $t (keys %$fk)
216    {
217        foreach my $c ( keys %{ $fk->{$t} } )
218        {
219            foreach my $key ( @{ $fk->{$t}{$c} } )
220            {
221                next if $seen{$key};
222                push @fk, $key;
223                $seen{$key} = 1;
224            }
225        }
226    }
227
228    return wantarray ? @fk : $fk[0];
229}
230
231sub index
232{
233    my $self = shift;
234
235    validate_pos( @_, { type => SCALAR } );
236    my $id = shift;
237
238    Alzabo::Exception::Params->throw( error => "Index $id doesn't exist in $self->{name}" )
239        unless $self->{indexes}->EXISTS($id);
240
241    return $self->{indexes}->FETCH($id);
242}
243
244sub has_index
245{
246    my $self = shift;
247
248    validate_pos( @_, { type => SCALAR } );
249    my $id = shift;
250
251    return $self->{indexes}->EXISTS($id);
252}
253
254sub indexes
255{
256    my $self = shift;
257
258    return $self->{indexes}->Values;
259}
260
261sub comment { $_[0]->{comment} }
262
263__END__
264
265=head1 NAME
266
267Alzabo::Table - Table objects
268
269=head1 SYNOPSIS
270
271  use Alzabo::Table;
272
273  my $t = $schema->table('foo');
274
275  foreach $pk ($t->primary_keys)
276  {
277     print $pk->name;
278  }
279
280=head1 DESCRIPTION
281
282Objects in this class represent tables.  They contain foreign key,
283index, and column objects.
284
285=head1 METHODS
286
287=head2 schema
288
289Returns the L<C<Alzabo::Schema>|Alzabo::Schema> object to which this
290table belongs.
291
292=head2 name
293
294Returns the name of the table.
295
296=head2 column ($name)
297
298Returns the L<C<Alzabo::Column>|Alzabo::Column> object that matches
299the name given.
300
301An L<C<Alzabo::Exception::Params>|Alzabo::Exceptions> exception is
302throws if the table does not contain the column.
303
304=head2 columns (@optional_list_of_column_names)
305
306If no arguments are given, returns a list of all
307L<C<Alzabo::Column>|Alzabo::Column> objects in the schema, or in a
308scalar context the number of such tables.  If one or more arguments
309are given, returns a list of table objects with those names, in the
310same order given.
311
312An L<C<Alzabo::Exception::Params>|Alzabo::Exceptions> exception is
313throws if the table does not contain one or more of the specified
314columns.
315
316=head2 has_column ($name)
317
318Returns a voolean value indicating whether the column exists in the
319table.
320
321=head2 primary_key
322
323In array context, return an ordered list of column objects that make
324up the primary key for the table.  In scalar context, it returns the
325first element of that list.
326
327=head2 primary_key_size
328
329The number of columns in the table's primary key.
330
331=head2 column_is_primary_key (C<Alzabo::Column> object)
332
333Returns a boolean value indicating whether the column given is part of
334the table's primary key.
335
336This method is really only needed if you're not sure that the column
337belongs to the table.  Otherwise just call the
338L<C<< Alzabo::Column->is_primary_key >>|Alzabo::Column/is_primary_key>
339method on the column object.
340
341=head2 attributes
342
343A table's attributes are strings describing the table (for example,
344valid attributes in MySQL are thing like "TYPE = INNODB".
345
346Returns a list of strings.
347
348=head2 has_attribute
349
350This method can be used to test whether or not a table has a
351particular attribute.  By default, the check is case-insensitive.
352
353=over 4
354
355=item * attribute => $attribute
356
357=item * case_sensitive => 0 or 1 (defaults to 0)
358
359=back
360
361Returns a boolean value indicating whether the table has this
362particular attribute.
363
364=head2 foreign_keys
365
366Thie method takes two parameters:
367
368=over 4
369
370=item * column => C<Alzabo::Column> object
371
372=item * table  => C<Alzabo::Table> object
373
374=back
375
376It returns a list of L<C<Alzabo::ForeignKey>|Alzabo::ForeignKey>
377objects B<from> the given column B<to> the given table, if they exist.
378In scalar context, it returns the first item in the list.  There is no
379guarantee as to what the first item will be.
380
381An L<C<Alzabo::Exception::Params>|Alzabo::Exceptions> exception is
382throws if the table does not contain the specified column.
383
384=head2 foreign_keys_by_table (C<Alzabo::Table> object)
385
386Returns a list of all the L<C<Alzabo::ForeignKey>|Alzabo::ForeignKey>
387objects B<to> the given table.  In scalar context, it returns the
388first item in the list.  There is no guarantee as to what the first
389item will be.
390
391=head2 foreign_keys_by_column (C<Alzabo::Column> object)
392
393Returns a list of all the L<C<Alzabo::ForeignKey>|Alzabo::ForeignKey>
394objects that the given column is a part of, if any.  In scalar
395context, it returns the first item in the list.  There is no guarantee
396as to what the first item will be.
397
398An L<C<Alzabo::Exception::Params>|Alzabo::Exceptions> exception is
399throws if the table does not contain the specified column.
400
401=head2 all_foreign_keys
402
403Returns a list of all the L<C<Alzabo::ForeignKey>|Alzabo::ForeignKey>
404objects for this table.  In scalar context, it returns the first item
405in the list.  There is no guarantee as to what the first item will be.
406
407=head2 index ($index_id)
408
409This method expects an index id as returned by the
410L<C<Alzabo::Index-E<gt>id>|Alzabo::Index/id> method as its parameter.
411
412The L<C<Alzabo::Index>|Alzabo::Index> object matching this id, if it
413exists in the table.
414
415An L<C<Alzabo::Exception::Params>|Alzabo::Exceptions> exception is
416throws if the table does not contain the specified index.
417
418=head2 has_index ($index_id)
419
420This method expects an index id as returned by the
421L<C<Alzabo::Index-E<gt>id>|Alzabo::Index/id> method as its parameter.
422
423Returns a boolean indicating whether the table has an index with the
424same id.
425
426=head2 indexes
427
428Returns all the L<C<Alzabo::Index>|Alzabo::Index> objects for the
429table.
430
431=head2 comment
432
433Returns the comment associated with the table object, if any.
434
435=head1 AUTHOR
436
437Dave Rolsky, <autarch@urth.org>
438
439=cut
440