index updating now can rename indices on Pg v8+, for efficiency with large data sets
[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.04';
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 sql_create_table [ DATABASE_HANDLE | DATA_SOURCE [ USERNAME PASSWORD [ ATTR ] ] ]
456
457 Returns a list of SQL statments to create this table.
458
459 Optionally, the data source can be specified by passing an open DBI database
460 handle, or by passing the DBI data source name, username and password.  
461
462 The data source can be specified by passing an open DBI database handle, or by
463 passing the DBI data source name, username and password.  
464
465 Although the username and password are optional, it is best to call this method
466 with a database handle or data source including a valid username and password -
467 a DBI connection will be opened and the quoting and type mapping will be more
468 reliable.
469
470 If passed a DBI data source (or handle) such as `DBI:mysql:database', will use
471 MySQL- or PostgreSQL-specific syntax.  Non-standard syntax for other engines
472 (if applicable) may also be supported in the future.
473
474 =cut
475
476 sub sql_create_table { 
477   my($self, $dbh) = ( shift, _dbh(@_) );
478
479   my $driver = _load_driver($dbh);
480
481 #should be in the DBD somehwere :/
482 #  my $saved_pkey = '';
483 #  if ( $driver eq 'Pg' && $self->primary_key ) {
484 #    my $pcolumn = $self->column( (
485 #      grep { $self->column($_)->name eq $self->primary_key } $self->columns
486 #    )[0] );
487 ##AUTO-INCREMENT#    $pcolumn->type('serial') if lc($pcolumn->type) eq 'integer';
488 #    $pcolumn->local( $pcolumn->local. ' PRIMARY KEY' );
489 #    #my $saved_pkey = $self->primary_key;
490 #    #$self->primary_key('');
491 #    #change it back afterwords :/
492 #  }
493
494   my @columns = map { $self->column($_)->line($dbh) } $self->columns;
495
496   push @columns, "PRIMARY KEY (". $self->primary_key. ")"
497     if $self->primary_key && ! grep /PRIMARY KEY/i, @columns;
498
499   my $indexnum = 1;
500
501   my @r = (
502     "CREATE TABLE ". $self->name. " (\n  ". join(",\n  ", @columns). "\n)\n"
503   );
504
505   if ( $self->unique ) {
506
507     warn "WARNING: DBIx::DBSchema::Table object for ". $self->name.
508          " table has deprecated (non-named) unique indices\n";
509
510     push @r, map {
511                    #my($index) = $self->name. "__". $_ . "_idx";
512                    #$index =~ s/,\s*/_/g;
513                    my $index = $self->name. $indexnum++;
514                    "CREATE UNIQUE INDEX $index ON ". $self->name. " ($_)\n"
515                  } $self->unique->sql_list;
516
517   }
518
519   if ( $self->index ) {
520
521     warn "WARNING: DBIx::DBSchema::Table object for ". $self->name.
522          " table has deprecated (non-named) indices\n";
523
524     push @r, map {
525                    #my($index) = $self->name. "__". $_ . "_idx";
526                    #$index =~ s/,\s*/_/g;
527                    my $index = $self->name. $indexnum++;
528                    "CREATE INDEX $index ON ". $self->name. " ($_)\n"
529                  } $self->index->sql_list;
530   }
531
532   my %indices = $self->indices;
533   #push @r, map { $indices{$_}->sql_create_index( $self->name ) } keys %indices;
534   foreach my $index ( keys %indices ) {
535     push @r, $indices{$index}->sql_create_index( $self->name );
536   }
537
538   #$self->primary_key($saved_pkey) if $saved_pkey;
539   @r;
540 }
541
542 =item sql_alter_table PROTOTYPE_TABLE, [ DATABASE_HANDLE | DATA_SOURCE [ USERNAME PASSWORD [ ATTR ] ] ]
543
544 Returns a list of SQL statements to alter this table so that it is identical
545 to the provided table, also a DBIx::DBSchema::Table object.
546
547  #Optionally, the data source can be specified by passing an open DBI database
548  #handle, or by passing the DBI data source name, username and password.  
549  #
550  #If passed a DBI data source (or handle) such as `DBI:Pg:dbname=database', will
551  #use PostgreSQL-specific syntax.  Non-standard syntax for other engines (if
552  #applicable) may also be supported in the future.
553  #
554  #If not passed a data source (or handle), or if there is no driver for the
555  #specified database, will attempt to use generic SQL syntax.
556
557 =cut
558
559 #gosh, false laziness w/DBSchema::sql_update_schema
560
561 sub sql_alter_table {
562   my( $self, $new, $dbh ) = ( shift, shift, _dbh(@_) );
563
564   my $driver = _load_driver($dbh);
565
566   my $table = $self->name;
567
568   my @r = ();
569   my @r_later = ();
570   my $tempnum = 1;
571
572   ###
573   # columns
574   ###
575
576   foreach my $column ( $new->columns ) {
577
578     if ( $self->column($column) )  {
579
580       warn "  $table.$column exists\n" if $DEBUG > 1;
581
582       push @r,
583         $self->column($column)->sql_alter_column( $new->column($column), $dbh );
584
585     } else {
586   
587       warn "column $table.$column does not exist.\n" if $DEBUG > 1;
588
589       push @r,
590         $new->column($column)->sql_add_column( $dbh );
591   
592     }
593   
594   }
595
596   #should eventually drop columns not in $new...
597   
598   ###
599   # indices
600   ###
601
602   my %old_indices = $self->indices;
603   my %new_indices = $new->indices;
604
605   foreach my $old ( keys %old_indices ) {
606
607     if ( exists( $new_indices{$old} )
608          && $old_indices{$old}->cmp( $new_indices{$old} )
609        )
610     {
611       warn "index $table.$old is identical; not changing\n" if $DEBUG > 1;
612       delete $old_indices{$old};
613       delete $new_indices{$old};
614
615     } elsif ( $driver eq 'Pg' and $dbh->{'pg_server_version'} >= 80000 ) {
616
617       my @same = grep { $old_indices{$old}->cmp_noname( $new_indices{$_} ) }
618                       keys %new_indices;
619
620       if ( @same ) {
621
622         #warn if there's more than one?
623         my $same = shift @same;
624
625         warn "index $table.$old is identical to $same; renaming\n"
626           if $DEBUG > 1;
627
628         my $temp = 'dbs_temp'.$tempnum++;
629
630         push @r, "ALTER INDEX $old RENAME TO $temp";
631         push @r_later, "ALTER INDEX $temp RENAME TO $same";
632
633         delete $old_indices{$old};
634         delete $new_indices{$same};
635
636       }
637
638     }
639
640   }
641
642   foreach my $old ( keys %old_indices ) {
643     warn "removing obsolete index $table.$old ON ( ".
644          $old_indices{$old}->columns_sql. " )\n"
645       if $DEBUG > 1;
646     push @r, "DROP INDEX $old".
647              ( $driver eq 'mysql' ? " ON $table" : '');
648   }
649
650   foreach my $new ( keys %new_indices ) {
651     warn "creating new index $table.$new\n" if $DEBUG > 1;
652     push @r, $new_indices{$new}->sql_create_index($table);
653   }
654   
655   ###
656   # return the statements
657   ###
658   
659   push @r, @r_later;
660
661   warn join('', map "$_\n", @r)
662     if $DEBUG && @r;
663
664   @r;
665
666 }
667
668 sub _null_sth {
669   my($dbh, $table) = @_;
670   my $sth = $dbh->prepare("SELECT * FROM $table WHERE 1=0")
671     or die $dbh->errstr;
672   $sth->execute or die $sth->errstr;
673   $sth;
674 }
675
676 =back
677
678 =head1 AUTHOR
679
680 Ivan Kohler <ivan-dbix-dbschema@420.am>
681
682 Thanks to Mark Ethan Trostler <mark@zzo.com> for a patch to allow tables
683 with no indices.
684
685 =head1 COPYRIGHT
686
687 Copyright (c) 2000-2007 Ivan Kohler
688 Copyright (c) 2000 Mail Abuse Prevention System LLC
689 Copyright (c) 2007 Freeside Internet Services, Inc.
690 All rights reserved.
691 This program is free software; you can redistribute it and/or modify it under
692 the same terms as Perl itself.
693
694 =head1 BUGS
695
696 sql_create_table() has database-specific foo that probably ought to be
697 abstracted into the DBIx::DBSchema::DBD:: modules (or no?  it doesn't anymore?).
698
699 sql_alter_table() also has database-specific foo that ought to be abstracted
700 into the DBIx::DBSchema::DBD:: modules.
701
702 sql_create_table() may change or destroy the object's data.  If you need to use
703 the object after sql_create_table, make a copy beforehand.
704
705 Some of the logic in new_odbc might be better abstracted into Column.pm etc.
706
707 sql_alter_table ought to drop columns not in $new
708
709 Add methods to get and set specific indices, by name? (like column COLUMN_NAME)
710
711 indices method should be a setter, not just a getter?
712
713 =head1 SEE ALSO
714
715 L<DBIx::DBSchema>, L<DBIx::DBSchema::ColGroup::Unique>,
716 L<DBIx::DBSchema::ColGroup::Index>, L<DBIx::DBSchema::Column>, L<DBI>
717
718 =cut
719
720 1;
721