drop table patch from Slaven, closes: CPAN#27936
[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,$value)=@_;
382
383   carp ref($self). "->unique method is deprecated; see ->indices";
384   #croak ref($self). "->unique method is deprecated; see ->indices";
385
386   if ( defined($value) ) {
387     $self->{unique} = $value;
388   } else {
389     $self->{unique};
390   }
391 }
392
393 =item index [ INDEX ]
394
395 This method is deprecated and included for backwards-compatibility only.
396 See L</indices> for the current method to access unique and non-unique index
397 objects.
398
399 Returns or sets the DBIx::DBSchema::ColGroup::Index object.
400
401 =cut
402
403 sub index { 
404   my($self,$value)=@_;
405
406   carp ref($self). "->index method is deprecated; see ->indices";
407   #croak ref($self). "->index method is deprecated; see ->indices";
408
409   if ( defined($value) ) {
410     $self->{'index'} = $value;
411   } else {
412     $self->{'index'};
413   }
414 }
415
416 =item columns
417
418 Returns a list consisting of the names of all columns.
419
420 =cut
421
422 sub columns {
423   my($self)=@_;
424   #keys %{$self->{'columns'}};
425   #must preserve order
426   @{ $self->{'column_order'} };
427 }
428
429 =item column COLUMN_NAME
430
431 Returns the column object (see L<DBIx::DBSchema::Column>) for the specified
432 COLUMN_NAME.
433
434 =cut
435
436 sub column {
437   my($self,$column)=@_;
438   $self->{'columns'}->{$column};
439 }
440
441 =item indices COLUMN_NAME
442
443 Returns a list of key-value pairs suitable for assigning to a hash.  Keys are
444 index names, and values are index objects (see L<DBIx::DBSchema::Index>).
445
446 =cut
447
448 sub indices {
449   my $self = shift;
450   exists( $self->{'indices'} )
451     ? %{ $self->{'indices'} }
452     : ();
453 }
454
455 =item unique_singles
456
457 Meet exciting and unique singles using this method!
458
459 This method returns a list of column names that are indexed with their own,
460 unique, non-compond (that's the "single" part) indices.
461
462 =cut
463
464 sub unique_singles {
465   my $self = shift;
466   my %indices = $self->indices;
467
468   map { ${ $indices{$_}->columns }[0] }
469       grep { $indices{$_}->unique && scalar(@{$indices{$_}->columns}) == 1 }
470            keys %indices;
471 }
472
473 =item sql_create_table [ DATABASE_HANDLE | DATA_SOURCE [ USERNAME PASSWORD [ ATTR ] ] ]
474
475 Returns a list of SQL statments to create this table.
476
477 Optionally, the data source can be specified by passing an open DBI database
478 handle, or by passing the DBI data source name, username and password.  
479
480 The data source can be specified by passing an open DBI database handle, or by
481 passing the DBI data source name, username and password.  
482
483 Although the username and password are optional, it is best to call this method
484 with a database handle or data source including a valid username and password -
485 a DBI connection will be opened and the quoting and type mapping will be more
486 reliable.
487
488 If passed a DBI data source (or handle) such as `DBI:mysql:database', will use
489 MySQL- or PostgreSQL-specific syntax.  Non-standard syntax for other engines
490 (if applicable) may also be supported in the future.
491
492 =cut
493
494 sub sql_create_table { 
495   my($self, $dbh) = ( shift, _dbh(@_) );
496
497   my $driver = _load_driver($dbh);
498
499 #should be in the DBD somehwere :/
500 #  my $saved_pkey = '';
501 #  if ( $driver eq 'Pg' && $self->primary_key ) {
502 #    my $pcolumn = $self->column( (
503 #      grep { $self->column($_)->name eq $self->primary_key } $self->columns
504 #    )[0] );
505 ##AUTO-INCREMENT#    $pcolumn->type('serial') if lc($pcolumn->type) eq 'integer';
506 #    $pcolumn->local( $pcolumn->local. ' PRIMARY KEY' );
507 #    #my $saved_pkey = $self->primary_key;
508 #    #$self->primary_key('');
509 #    #change it back afterwords :/
510 #  }
511
512   my @columns = map { $self->column($_)->line($dbh) } $self->columns;
513
514   push @columns, "PRIMARY KEY (". $self->primary_key. ")"
515     if $self->primary_key && ! grep /PRIMARY KEY/i, @columns;
516
517   my $indexnum = 1;
518
519   my @r = (
520     "CREATE TABLE ". $self->name. " (\n  ". join(",\n  ", @columns). "\n)\n"
521   );
522
523   if ( $self->unique ) {
524
525     warn "WARNING: DBIx::DBSchema::Table object for ". $self->name.
526          " table has deprecated (non-named) unique indices\n";
527
528     push @r, map {
529                    #my($index) = $self->name. "__". $_ . "_idx";
530                    #$index =~ s/,\s*/_/g;
531                    my $index = $self->name. $indexnum++;
532                    "CREATE UNIQUE INDEX $index ON ". $self->name. " ($_)\n"
533                  } $self->unique->sql_list;
534
535   }
536
537   if ( $self->index ) {
538
539     warn "WARNING: DBIx::DBSchema::Table object for ". $self->name.
540          " table has deprecated (non-named) 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 INDEX $index ON ". $self->name. " ($_)\n"
547                  } $self->index->sql_list;
548   }
549
550   my %indices = $self->indices;
551   #push @r, map { $indices{$_}->sql_create_index( $self->name ) } keys %indices;
552   foreach my $index ( keys %indices ) {
553     push @r, $indices{$index}->sql_create_index( $self->name );
554   }
555
556   #$self->primary_key($saved_pkey) if $saved_pkey;
557   @r;
558 }
559
560 =item sql_alter_table PROTOTYPE_TABLE, [ DATABASE_HANDLE | DATA_SOURCE [ USERNAME PASSWORD [ ATTR ] ] ]
561
562 Returns a list of SQL statements to alter this table so that it is identical
563 to the provided table, also a DBIx::DBSchema::Table object.
564
565  #Optionally, the data source can be specified by passing an open DBI database
566  #handle, or by passing the DBI data source name, username and password.  
567  #
568  #If passed a DBI data source (or handle) such as `DBI:Pg:dbname=database', will
569  #use PostgreSQL-specific syntax.  Non-standard syntax for other engines (if
570  #applicable) may also be supported in the future.
571  #
572  #If not passed a data source (or handle), or if there is no driver for the
573  #specified database, will attempt to use generic SQL syntax.
574
575 =cut
576
577 #gosh, false laziness w/DBSchema::sql_update_schema
578
579 sub sql_alter_table {
580   my( $self, $new, $dbh ) = ( shift, shift, _dbh(@_) );
581
582   my $driver = _load_driver($dbh);
583
584   my $table = $self->name;
585
586   my @r = ();
587   my @r_later = ();
588   my $tempnum = 1;
589
590   ###
591   # columns
592   ###
593
594   foreach my $column ( $new->columns ) {
595
596     if ( $self->column($column) )  {
597
598       warn "  $table.$column exists\n" if $DEBUG > 1;
599
600       push @r,
601         $self->column($column)->sql_alter_column( $new->column($column), $dbh );
602
603     } else {
604   
605       warn "column $table.$column does not exist.\n" if $DEBUG > 1;
606
607       push @r,
608         $new->column($column)->sql_add_column( $dbh );
609   
610     }
611   
612   }
613
614   #should eventually drop columns not in $new...
615   
616   ###
617   # indices
618   ###
619
620   my %old_indices = $self->indices;
621   my %new_indices = $new->indices;
622
623   foreach my $old ( keys %old_indices ) {
624
625     if ( exists( $new_indices{$old} )
626          && $old_indices{$old}->cmp( $new_indices{$old} )
627        )
628     {
629       warn "index $table.$old is identical; not changing\n" if $DEBUG > 1;
630       delete $old_indices{$old};
631       delete $new_indices{$old};
632
633     } elsif ( $driver eq 'Pg' and $dbh->{'pg_server_version'} >= 80000 ) {
634
635       my @same = grep { $old_indices{$old}->cmp_noname( $new_indices{$_} ) }
636                       keys %new_indices;
637
638       if ( @same ) {
639
640         #warn if there's more than one?
641         my $same = shift @same;
642
643         warn "index $table.$old is identical to $same; renaming\n"
644           if $DEBUG > 1;
645
646         my $temp = 'dbs_temp'.$tempnum++;
647
648         push @r, "ALTER INDEX $old RENAME TO $temp";
649         push @r_later, "ALTER INDEX $temp RENAME TO $same";
650
651         delete $old_indices{$old};
652         delete $new_indices{$same};
653
654       }
655
656     }
657
658   }
659
660   foreach my $old ( keys %old_indices ) {
661     warn "removing obsolete index $table.$old ON ( ".
662          $old_indices{$old}->columns_sql. " )\n"
663       if $DEBUG > 1;
664     push @r, "DROP INDEX $old".
665              ( $driver eq 'mysql' ? " ON $table" : '');
666   }
667
668   foreach my $new ( keys %new_indices ) {
669     warn "creating new index $table.$new\n" if $DEBUG > 1;
670     push @r, $new_indices{$new}->sql_create_index($table);
671   }
672   
673   ###
674   # return the statements
675   ###
676   
677   push @r, @r_later;
678
679   warn join('', map "$_\n", @r)
680     if $DEBUG && @r;
681
682   @r;
683
684 }
685
686 sub sql_drop_table {
687   my( $self, $dbh ) = ( shift, _dbh(@_) );
688
689   my $name = $self->name;
690
691   ("DROP TABLE $name");
692 }
693
694 sub _null_sth {
695   my($dbh, $table) = @_;
696   my $sth = $dbh->prepare("SELECT * FROM $table WHERE 1=0")
697     or die $dbh->errstr;
698   $sth->execute or die $sth->errstr;
699   $sth;
700 }
701
702 =back
703
704 =head1 AUTHOR
705
706 Ivan Kohler <ivan-dbix-dbschema@420.am>
707
708 Thanks to Mark Ethan Trostler <mark@zzo.com> for a patch to allow tables
709 with no indices.
710
711 =head1 COPYRIGHT
712
713 Copyright (c) 2000-2007 Ivan Kohler
714 Copyright (c) 2000 Mail Abuse Prevention System LLC
715 Copyright (c) 2007 Freeside Internet Services, Inc.
716 All rights reserved.
717 This program is free software; you can redistribute it and/or modify it under
718 the same terms as Perl itself.
719
720 =head1 BUGS
721
722 sql_create_table() has database-specific foo that probably ought to be
723 abstracted into the DBIx::DBSchema::DBD:: modules (or no?  it doesn't anymore?).
724
725 sql_alter_table() also has database-specific foo that ought to be abstracted
726 into the DBIx::DBSchema::DBD:: modules.
727
728 sql_create_table() may change or destroy the object's data.  If you need to use
729 the object after sql_create_table, make a copy beforehand.
730
731 Some of the logic in new_odbc might be better abstracted into Column.pm etc.
732
733 sql_alter_table ought to drop columns not in $new
734
735 Add methods to get and set specific indices, by name? (like column COLUMN_NAME)
736
737 indices method should be a setter, not just a getter?
738
739 =head1 SEE ALSO
740
741 L<DBIx::DBSchema>, L<DBIx::DBSchema::ColGroup::Unique>,
742 L<DBIx::DBSchema::ColGroup::Index>, L<DBIx::DBSchema::Column>, L<DBI>
743
744 =cut
745
746 1;
747