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