899fe94267956d52b0d61afa0db1a832da7c7cd5
[DBIx-DBSchema.git] / DBSchema / Table.pm
1 package DBIx::DBSchema::Table;
2
3 use strict;
4 use vars qw($VERSION $DEBUG %create_params);
5 use Carp;
6 #use Exporter;
7 use DBIx::DBSchema::_util qw(_load_driver _dbh);
8 use DBIx::DBSchema::Column 0.07;
9 use DBIx::DBSchema::Index;
10 use DBIx::DBSchema::ColGroup::Unique;
11 use DBIx::DBSchema::ColGroup::Index;
12
13 $VERSION = '0.05';
14 $DEBUG = 0;
15
16 =head1 NAME
17
18 DBIx::DBSchema::Table - Table objects
19
20 =head1 SYNOPSIS
21
22   use DBIx::DBSchema::Table;
23
24   #new style (preferred), pass a hashref of parameters
25   $table = new DBIx::DBSchema::Table (
26     {
27       name        => "table_name",
28       primary_key => "primary_key",
29       columns     => \@dbix_dbschema_column_objects,
30       #deprecated# unique      => $dbix_dbschema_colgroup_unique_object,
31       #deprecated# 'index'     => $dbix_dbschema_colgroup_index_object,
32       indices     => \@dbix_dbschema_index_objects,
33     }
34   );
35
36   #old style (VERY deprecated)
37   $table = new DBIx::DBSchema::Table (
38     "table_name",
39     "primary_key",
40     $dbix_dbschema_colgroup_unique_object,
41     $dbix_dbschema_colgroup_index_object,
42     @dbix_dbschema_column_objects,
43   );
44
45   $table->addcolumn ( $dbix_dbschema_column_object );
46
47   $table_name = $table->name;
48   $table->name("table_name");
49
50   $primary_key = $table->primary_key;
51   $table->primary_key("primary_key");
52
53   #deprecated# $dbix_dbschema_colgroup_unique_object = $table->unique;
54   #deprecated# $table->unique( $dbix_dbschema__colgroup_unique_object );
55
56   #deprecated# $dbix_dbschema_colgroup_index_object = $table->index;
57   #deprecated# $table->index( $dbix_dbschema_colgroup_index_object );
58
59   %indices = $table->indices;
60   $dbix_dbschema_index_object = $indices{'index_name'};
61   @all_index_names = keys %indices;
62   @all_dbix_dbschema_index_objects = values %indices;
63
64   @column_names = $table->columns;
65
66   $dbix_dbschema_column_object = $table->column("column");
67
68   #preferred
69   @sql_statements = $table->sql_create_table( $dbh );
70   @sql_statements = $table->sql_create_table( $datasrc, $username, $password );
71
72   #possible problems
73   @sql_statements = $table->sql_create_table( $datasrc );
74   @sql_statements = $table->sql_create_table;
75
76 =head1 DESCRIPTION
77
78 DBIx::DBSchema::Table objects represent a single database table.
79
80 =head1 METHODS
81
82 =over 4
83
84 =item new HASHREF
85
86 Creates a new DBIx::DBSchema::Table object.  The preferred usage is to pass a
87 hash reference of named parameters.
88
89   {
90     name        => TABLE_NAME,
91     primary_key => PRIMARY_KEY,
92     columns     => COLUMNS,
93     indices     => INDICES,
94     #deprecated# unique => UNIQUE,
95     #deprecated# index  => INDEX,
96   }
97
98 TABLE_NAME is the name of the table.  PRIMARY_KEY is the primary key (may be
99 empty).  COLUMNS is a reference to an array of DBIx::DBSchema::Column objects
100 (see L<DBIx::DBSchema::Column>).  INDICES is a reference to an array of 
101 DBIx::DBSchema::Index objects (see L<DBIx::DBSchema::Index>), or a hash
102 reference of index names (keys) and DBIx::DBSchema::Index objects (values).
103
104 Deprecated options:
105
106 UNIQUE was a DBIx::DBSchema::ColGroup::Unique object (see
107 L<DBIx::DBSchema::ColGroup::Unique>).  INDEX was a
108 DBIx::DBSchema::ColGroup::Index object (see
109 L<DBIx::DBSchema::ColGroup::Index>).
110
111 =cut
112
113 sub new {
114   my $proto = shift;
115   my $class = ref($proto) || $proto;
116
117   my $self;
118   if ( ref($_[0]) ) {
119
120     $self = shift;
121     $self->{column_order} = [ map { $_->name } @{$self->{columns}} ];
122     $self->{columns} = { map { $_->name, $_ } @{$self->{columns}} };
123
124     $self->{indices} = { map { $_->name, $_ } @{$self->{indices}} }
125        if ref($self->{indices}) eq 'ARRAY';
126
127   } else {
128
129     carp "Old-style $class creation without named parameters is deprecated!";
130     #croak "FATAL: old-style $class creation no longer supported;".
131     #      " use named parameters";
132
133     my($name,$primary_key,$unique,$index,@columns) = @_;
134
135     my %columns = map { $_->name, $_ } @columns;
136     my @column_order = map { $_->name } @columns;
137
138     $self = {
139       'name'         => $name,
140       'primary_key'  => $primary_key,
141       'unique'       => $unique,
142       'index'        => $index,
143       'columns'      => \%columns,
144       'column_order' => \@column_order,
145     };
146
147   }
148
149   #check $primary_key, $unique and $index to make sure they are $columns ?
150   # (and sanity check?)
151
152   bless ($self, $class);
153
154   $_->table_obj($self) foreach values %{ $self->{columns} };
155
156   $self;
157 }
158
159 =item new_odbc DATABASE_HANDLE TABLE_NAME
160
161 Creates a new DBIx::DBSchema::Table object from the supplied DBI database
162 handle for the specified table.  This uses the experimental DBI type_info
163 method to create a table with standard (ODBC) SQL column types that most
164 closely correspond to any non-portable column types.   Use this to import a
165 schema that you wish to use with many different database engines.  Although
166 primary key and (unique) index information will only be imported from databases
167 with DBIx::DBSchema::DBD drivers (currently MySQL and PostgreSQL), import of
168 column names and attributes *should* work for any database.
169
170 Note: the _odbc refers to the column types used and nothing else - you do not
171 have to have ODBC installed or connect to the database via ODBC.
172
173 =cut
174
175 %create_params = (
176 #  undef             => sub { '' },
177   ''                => sub { '' },
178   'max length'      => sub { $_[0]->{PRECISION}->[$_[1]]; },
179   'precision,scale' =>
180     sub { $_[0]->{PRECISION}->[$_[1]]. ','. $_[0]->{SCALE}->[$_[1]]; }
181 );
182
183 sub new_odbc {
184   my( $proto, $dbh, $name) = @_;
185
186   my $driver = _load_driver($dbh);
187   my $sth = _null_sth($dbh, $name);
188   my $sthpos = 0;
189
190   my $indices_hr =
191     ( $driver
192         ? eval "DBIx::DBSchema::DBD::$driver->indices(\$dbh, \$name)"
193         : {}
194     );
195
196   $proto->new({
197     'name'        => $name,
198     'primary_key' => scalar(eval "DBIx::DBSchema::DBD::$driver->primary_key(\$dbh, \$name)"),
199
200     'columns'     => [
201     
202       map { 
203
204             my $col_name = $_;
205
206             my $type_info = scalar($dbh->type_info($sth->{TYPE}->[$sthpos]))
207               or die "DBI::type_info ". $dbh->{Driver}->{Name}. " driver ".
208                      "returned no results for type ".  $sth->{TYPE}->[$sthpos];
209
210             my $length = &{ $create_params{ $type_info->{CREATE_PARAMS} } }
211                           ( $sth, $sthpos++ );
212
213             my $default = '';
214             if ( $driver ) {
215               $default = ${ [
216                 eval "DBIx::DBSchema::DBD::$driver->column(\$dbh, \$name, \$_)"
217               ] }[4];
218             }
219
220             DBIx::DBSchema::Column->new({
221                 'name'    => $col_name,
222                 #'type'    => "SQL_". uc($type_info->{'TYPE_NAME'}),
223                 'type'    => $type_info->{'TYPE_NAME'},
224                 'null'    => $sth->{NULLABLE}->[$sthpos],
225                 'length'  => $length,          
226                 'default' => $default,
227                 #'local'   => # DB-local
228             });
229
230           }
231           @{$sth->{NAME}}
232     
233     ],
234
235     #old-style indices
236     #DBIx::DBSchema::ColGroup::Unique->new(
237     #  $driver
238     #   ? [values %{eval "DBIx::DBSchema::DBD::$driver->unique(\$dbh, \$name)"}]
239     #   : []
240     #),
241     #DBIx::DBSchema::ColGroup::Index->new(
242     #  $driver
243     #  ? [ values %{eval "DBIx::DBSchema::DBD::$driver->index(\$dbh, \$name)"} ]
244     #  : []
245     #),
246
247     #new-style indices
248     'indices' => { map { my $indexname = $_;
249                          $indexname =>
250                            DBIx::DBSchema::Index->new($indices_hr->{$indexname})
251                        } 
252                        keys %$indices_hr
253                  },
254
255   });
256 }
257
258 =item new_native DATABASE_HANDLE TABLE_NAME
259
260 Creates a new DBIx::DBSchema::Table object from the supplied DBI database
261 handle for the specified table.  This uses database-native methods to read the
262 schema, and will preserve any non-portable column types.  The method is only
263 available if there is a DBIx::DBSchema::DBD for the corresponding database
264 engine (currently, MySQL and PostgreSQL).
265
266 =cut
267
268 sub new_native {
269   my( $proto, $dbh, $name) = @_;
270   my $driver = _load_driver($dbh);
271
272   my $indices_hr =
273   ( $driver
274       ? eval "DBIx::DBSchema::DBD::$driver->indices(\$dbh, \$name)"
275       : {}
276   );
277
278   $proto->new({
279     'name'        => $name,
280     'primary_key' => scalar(eval "DBIx::DBSchema::DBD::$driver->primary_key(\$dbh, \$name)"),
281     'columns'     => [
282     
283       map DBIx::DBSchema::Column->new( @{$_} ),
284           eval "DBIx::DBSchema::DBD::$driver->columns(\$dbh, \$name)"
285     ],
286
287     #old-style indices
288     #DBIx::DBSchema::ColGroup::Unique->new(
289     #  [ values %{eval "DBIx::DBSchema::DBD::$driver->unique(\$dbh, \$name)"} ]
290     #),
291     #DBIx::DBSchema::ColGroup::Index->new(
292     #  [ values %{eval "DBIx::DBSchema::DBD::$driver->index(\$dbh, \$name)"} ]
293     #),
294     
295     #new-style indices
296     'indices' => { map { my $indexname = $_;
297                          $indexname =>
298                            DBIx::DBSchema::Index->new($indices_hr->{$indexname})
299                        } 
300                        keys %$indices_hr
301                  },
302
303   });
304 }
305
306 =item addcolumn COLUMN
307
308 Adds this DBIx::DBSchema::Column object. 
309
310 =cut
311
312 sub addcolumn {
313   my($self, $column) = @_;
314   $column->table_obj($self);
315   ${$self->{'columns'}}{$column->name} = $column; #sanity check?
316   push @{$self->{'column_order'}}, $column->name;
317 }
318
319 =item delcolumn COLUMN_NAME
320
321 Deletes this column.  Returns false if no column of this name was found to
322 remove, true otherwise.
323
324 =cut
325
326 sub delcolumn {
327   my($self,$column) = @_;
328   return 0 unless exists $self->{'columns'}{$column};
329   $self->{'columns'}{$column}->table_obj('');
330   delete $self->{'columns'}{$column};
331   @{$self->{'column_order'}}= grep { $_ ne $column } @{$self->{'column_order'}};  1;
332 }
333
334 =item name [ TABLE_NAME ]
335
336 Returns or sets the table name.
337
338 =cut
339
340 sub name {
341   my($self,$value)=@_;
342   if ( defined($value) ) {
343     $self->{name} = $value;
344   } else {
345     $self->{name};
346   }
347 }
348
349 =item primary_key [ PRIMARY_KEY ]
350
351 Returns or sets the primary key.
352
353 =cut
354
355 sub primary_key {
356   my($self,$value)=@_;
357   if ( defined($value) ) {
358     $self->{primary_key} = $value;
359   } else {
360     #$self->{primary_key};
361     #hmm.  maybe should untaint the entire structure when it comes off disk 
362     # cause if you don't trust that, ?
363     $self->{primary_key} =~ /^(\w*)$/ 
364       #aah!
365       or die "Illegal primary key: ", $self->{primary_key};
366     $1;
367   }
368 }
369
370 =item unique [ UNIQUE ]
371
372 This method is deprecated and included for backwards-compatibility only.
373 See L</indices> for the current method to access unique and non-unique index
374 objects.
375
376 Returns or sets the DBIx::DBSchema::ColGroup::Unique object.
377
378 =cut
379
380 sub unique {
381     my $self = shift;
382
383     carp ref($self) . "->unique method is deprecated; see ->indices";
384     #croak ref($self). "->unique method is deprecated; see ->indices";
385
386     $self->_unique(@_);
387 }
388
389 sub _unique {
390
391   my ($self,$value)=@_;
392
393   if ( defined($value) ) {
394     $self->{unique} = $value;
395   } else {
396     $self->{unique};
397   }
398 }
399
400 =item index [ INDEX ]
401
402 This method is deprecated and included for backwards-compatibility only.
403 See L</indices> for the current method to access unique and non-unique index
404 objects.
405
406 Returns or sets the DBIx::DBSchema::ColGroup::Index object.
407
408 =cut
409
410 sub index { 
411   my $self = shift;
412
413   carp ref($self). "->index method is deprecated; see ->indices";
414   #croak ref($self). "->index method is deprecated; see ->indices";
415
416   $self->_index(@_);
417 }
418
419
420 sub _index {
421   my($self,$value)=@_;
422
423   if ( defined($value) ) {
424     $self->{'index'} = $value;
425   } else {
426     $self->{'index'};
427   }
428 }
429
430 =item columns
431
432 Returns a list consisting of the names of all columns.
433
434 =cut
435
436 sub columns {
437   my($self)=@_;
438   #keys %{$self->{'columns'}};
439   #must preserve order
440   @{ $self->{'column_order'} };
441 }
442
443 =item column COLUMN_NAME
444
445 Returns the column object (see L<DBIx::DBSchema::Column>) for the specified
446 COLUMN_NAME.
447
448 =cut
449
450 sub column {
451   my($self,$column)=@_;
452   $self->{'columns'}->{$column};
453 }
454
455 =item indices COLUMN_NAME
456
457 Returns a list of key-value pairs suitable for assigning to a hash.  Keys are
458 index names, and values are index objects (see L<DBIx::DBSchema::Index>).
459
460 =cut
461
462 sub indices {
463   my $self = shift;
464   exists( $self->{'indices'} )
465     ? %{ $self->{'indices'} }
466     : ();
467 }
468
469 =item unique_singles
470
471 Meet exciting and unique singles using this method!
472
473 This method returns a list of column names that are indexed with their own,
474 unique, non-compond (that's the "single" part) indices.
475
476 =cut
477
478 sub unique_singles {
479   my $self = shift;
480   my %indices = $self->indices;
481
482   map { ${ $indices{$_}->columns }[0] }
483       grep { $indices{$_}->unique && scalar(@{$indices{$_}->columns}) == 1 }
484            keys %indices;
485 }
486
487 =item sql_create_table [ DATABASE_HANDLE | DATA_SOURCE [ USERNAME PASSWORD [ ATTR ] ] ]
488
489 Returns a list of SQL statments to create this table.
490
491 Optionally, the data source can be specified by passing an open DBI database
492 handle, or by passing the DBI data source name, username and password.  
493
494 The data source can be specified by passing an open DBI database handle, or by
495 passing the DBI data source name, username and password.  
496
497 Although the username and password are optional, it is best to call this method
498 with a database handle or data source including a valid username and password -
499 a DBI connection will be opened and the quoting and type mapping will be more
500 reliable.
501
502 If passed a DBI data source (or handle) such as `DBI:mysql:database', will use
503 MySQL- or PostgreSQL-specific syntax.  Non-standard syntax for other engines
504 (if applicable) may also be supported in the future.
505
506 =cut
507
508 sub sql_create_table { 
509   my($self, $dbh) = ( shift, _dbh(@_) );
510
511   my $driver = _load_driver($dbh);
512
513 #should be in the DBD somehwere :/
514 #  my $saved_pkey = '';
515 #  if ( $driver eq 'Pg' && $self->primary_key ) {
516 #    my $pcolumn = $self->column( (
517 #      grep { $self->column($_)->name eq $self->primary_key } $self->columns
518 #    )[0] );
519 ##AUTO-INCREMENT#    $pcolumn->type('serial') if lc($pcolumn->type) eq 'integer';
520 #    $pcolumn->local( $pcolumn->local. ' PRIMARY KEY' );
521 #    #my $saved_pkey = $self->primary_key;
522 #    #$self->primary_key('');
523 #    #change it back afterwords :/
524 #  }
525
526   my @columns = map { $self->column($_)->line($dbh) } $self->columns;
527
528   push @columns, "PRIMARY KEY (". $self->primary_key. ")"
529     if $self->primary_key && ! grep /PRIMARY KEY/i, @columns;
530
531   my $indexnum = 1;
532
533   my @r = (
534     "CREATE TABLE ". $self->name. " (\n  ". join(",\n  ", @columns). "\n)\n"
535   );
536
537   if ( $self->_unique ) {
538
539     warn "WARNING: DBIx::DBSchema::Table object for ". $self->name.
540          " table has deprecated (non-named) unique indices\n";
541
542     push @r, map {
543                    #my($index) = $self->name. "__". $_ . "_idx";
544                    #$index =~ s/,\s*/_/g;
545                    my $index = $self->name. $indexnum++;
546                    "CREATE UNIQUE INDEX $index ON ". $self->name. " ($_)\n"
547                  } $self->unique->sql_list;
548
549   }
550
551   if ( $self->_index ) {
552
553     warn "WARNING: DBIx::DBSchema::Table object for ". $self->name.
554          " table has deprecated (non-named) indices\n";
555
556     push @r, map {
557                    #my($index) = $self->name. "__". $_ . "_idx";
558                    #$index =~ s/,\s*/_/g;
559                    my $index = $self->name. $indexnum++;
560                    "CREATE INDEX $index ON ". $self->name. " ($_)\n"
561                  } $self->index->sql_list;
562   }
563
564   my %indices = $self->indices;
565   #push @r, map { $indices{$_}->sql_create_index( $self->name ) } keys %indices;
566   foreach my $index ( keys %indices ) {
567     push @r, $indices{$index}->sql_create_index( $self->name );
568   }
569
570   #$self->primary_key($saved_pkey) if $saved_pkey;
571   @r;
572 }
573
574 =item sql_alter_table PROTOTYPE_TABLE, [ DATABASE_HANDLE | DATA_SOURCE [ USERNAME PASSWORD [ ATTR ] ] ]
575
576 Returns a list of SQL statements to alter this table so that it is identical
577 to the provided table, also a DBIx::DBSchema::Table object.
578
579 The data source can be specified by passing an open DBI database handle, or by
580 passing the DBI data source name, username and password.  
581
582 Although the username and password are optional, it is best to call this method
583 with a database handle or data source including a valid username and password -
584 a DBI connection will be opened and used to check the database version as well
585 as for more reliable quoting and type mapping.  Note that the database
586 connection will be used passively, B<not> to actually run the CREATE
587 statements.
588
589 If passed a DBI data source (or handle) such as `DBI:mysql:database' or
590 `DBI:Pg:dbname=database', will use syntax specific to that database engine.
591 Currently supported databases are MySQL and PostgreSQL.
592
593 If not passed a data source (or handle), or if there is no driver for the
594 specified database, will attempt to use generic SQL syntax.
595
596 =cut
597
598 #gosh, false laziness w/DBSchema::sql_update_schema
599
600 sub sql_alter_table {
601   my( $self, $new, $dbh ) = ( shift, shift, _dbh(@_) );
602
603   my $driver = _load_driver($dbh);
604
605   my $table = $self->name;
606
607   my @r = ();
608   my @r_later = ();
609   my $tempnum = 1;
610
611   ###
612   # columns
613   ###
614
615   foreach my $column ( $new->columns ) {
616
617     if ( $self->column($column) )  {
618
619       warn "  $table.$column exists\n" if $DEBUG > 1;
620
621       push @r,
622         $self->column($column)->sql_alter_column( $new->column($column), $dbh );
623
624     } else {
625   
626       warn "column $table.$column does not exist.\n" if $DEBUG > 1;
627
628       push @r,
629         $new->column($column)->sql_add_column( $dbh );
630   
631     }
632   
633   }
634
635   #should eventually drop columns not in $new...
636   
637   ###
638   # indices
639   ###
640
641   my %old_indices = $self->indices;
642   my %new_indices = $new->indices;
643
644   foreach my $old ( keys %old_indices ) {
645
646     if ( exists( $new_indices{$old} )
647          && $old_indices{$old}->cmp( $new_indices{$old} )
648        )
649     {
650       warn "index $table.$old is identical; not changing\n" if $DEBUG > 1;
651       delete $old_indices{$old};
652       delete $new_indices{$old};
653
654     } elsif ( $driver eq 'Pg' and $dbh->{'pg_server_version'} >= 80000 ) {
655
656       my @same = grep { $old_indices{$old}->cmp_noname( $new_indices{$_} ) }
657                       keys %new_indices;
658
659       if ( @same ) {
660
661         #warn if there's more than one?
662         my $same = shift @same;
663
664         warn "index $table.$old is identical to $same; renaming\n"
665           if $DEBUG > 1;
666
667         my $temp = 'dbs_temp'.$tempnum++;
668
669         push @r, "ALTER INDEX $old RENAME TO $temp";
670         push @r_later, "ALTER INDEX $temp RENAME TO $same";
671
672         delete $old_indices{$old};
673         delete $new_indices{$same};
674
675       }
676
677     }
678
679   }
680
681   foreach my $old ( keys %old_indices ) {
682     warn "removing obsolete index $table.$old ON ( ".
683          $old_indices{$old}->columns_sql. " )\n"
684       if $DEBUG > 1;
685     push @r, "DROP INDEX $old".
686              ( $driver eq 'mysql' ? " ON $table" : '');
687   }
688
689   foreach my $new ( keys %new_indices ) {
690     warn "creating new index $table.$new\n" if $DEBUG > 1;
691     push @r, $new_indices{$new}->sql_create_index($table);
692   }
693   
694   ###
695   # return the statements
696   ###
697   
698   push @r, @r_later;
699
700   warn join('', map "$_\n", @r)
701     if $DEBUG && @r;
702
703   @r;
704
705 }
706
707 sub sql_drop_table {
708   my( $self, $dbh ) = ( shift, _dbh(@_) );
709
710   my $name = $self->name;
711
712   ("DROP TABLE $name");
713 }
714
715 sub _null_sth {
716   my($dbh, $table) = @_;
717   my $sth = $dbh->prepare("SELECT * FROM $table WHERE 1=0")
718     or die $dbh->errstr;
719   $sth->execute or die $sth->errstr;
720   $sth;
721 }
722
723 =back
724
725 =head1 AUTHOR
726
727 Ivan Kohler <ivan-dbix-dbschema@420.am>
728
729 Thanks to Mark Ethan Trostler <mark@zzo.com> for a patch to allow tables
730 with no indices.
731
732 =head1 COPYRIGHT
733
734 Copyright (c) 2000-2007 Ivan Kohler
735 Copyright (c) 2000 Mail Abuse Prevention System LLC
736 Copyright (c) 2007 Freeside Internet Services, Inc.
737 All rights reserved.
738 This program is free software; you can redistribute it and/or modify it under
739 the same terms as Perl itself.
740
741 =head1 BUGS
742
743 sql_create_table() has database-specific foo that probably ought to be
744 abstracted into the DBIx::DBSchema::DBD:: modules (or no?  it doesn't anymore?).
745
746 sql_alter_table() also has database-specific foo that ought to be abstracted
747 into the DBIx::DBSchema::DBD:: modules.
748
749 sql_create_table() may change or destroy the object's data.  If you need to use
750 the object after sql_create_table, make a copy beforehand.
751
752 Some of the logic in new_odbc might be better abstracted into Column.pm etc.
753
754 sql_alter_table ought to drop columns not in $new
755
756 Add methods to get and set specific indices, by name? (like column COLUMN_NAME)
757
758 indices method should be a setter, not just a getter?
759
760 =head1 SEE ALSO
761
762 L<DBIx::DBSchema>, L<DBIx::DBSchema::ColGroup::Unique>,
763 L<DBIx::DBSchema::ColGroup::Index>, L<DBIx::DBSchema::Column>, L<DBI>
764
765 =cut
766
767 1;
768