b6296ece447ca276f0d4133e9da746a86e99368e
[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
570   ###
571   # columns
572   ###
573
574   foreach my $column ( $new->columns ) {
575
576     if ( $self->column($column) )  {
577
578       warn "  $table.$column exists\n" if $DEBUG > 1;
579
580       push @r,
581         $self->column($column)->sql_alter_column( $new->column($column), $dbh );
582
583     } else {
584   
585       warn "column $table.$column does not exist.\n" if $DEBUG > 1;
586
587       push @r,
588         $new->column($column)->sql_add_column( $dbh );
589   
590     }
591   
592   }
593
594   #should eventually drop columns not in $new...
595   
596   ###
597   # indices
598   ###
599
600   my %old_indices = $self->indices;
601   my %new_indices = $new->indices;
602
603   foreach my $old ( keys %old_indices ) {
604
605     if ( exists( $new_indices{$old} )
606          && $old_indices{$old}->cmp( $new_indices{$old} )
607        )
608     {
609       warn "index $table.$old is identical; not changing\n" if $DEBUG > 1;
610       delete $old_indices{$old};
611       delete $new_indices{$old};
612     }
613
614   }
615
616   foreach my $old ( keys %old_indices ) {
617     warn "removing obsolete index $table.$old ON ( ".
618          $old_indices{$old}->columns_sql. " )\n"
619       if $DEBUG > 1;
620     push @r, "DROP INDEX $old".
621              ( $driver eq 'mysql' ? " ON $table" : '');
622   }
623
624   foreach my $new ( keys %new_indices ) {
625     warn "creating new index $table.$new\n" if $DEBUG > 1;
626     push @r, $new_indices{$new}->sql_create_index($table);
627   }
628   
629   ###
630   # return the statements
631   ###
632
633   warn join('', map "$_\n", @r)
634     if $DEBUG && @r;
635
636   @r;
637
638 }
639
640 sub _null_sth {
641   my($dbh, $table) = @_;
642   my $sth = $dbh->prepare("SELECT * FROM $table WHERE 1=0")
643     or die $dbh->errstr;
644   $sth->execute or die $sth->errstr;
645   $sth;
646 }
647
648 =back
649
650 =head1 AUTHOR
651
652 Ivan Kohler <ivan-dbix-dbschema@420.am>
653
654 Thanks to Mark Ethan Trostler <mark@zzo.com> for a patch to allow tables
655 with no indices.
656
657 =head1 COPYRIGHT
658
659 Copyright (c) 2000-2007 Ivan Kohler
660 Copyright (c) 2000 Mail Abuse Prevention System LLC
661 Copyright (c) 2007 Freeside Internet Services, Inc.
662 All rights reserved.
663 This program is free software; you can redistribute it and/or modify it under
664 the same terms as Perl itself.
665
666 =head1 BUGS
667
668 sql_create_table() has database-specific foo that probably ought to be
669 abstracted into the DBIx::DBSchema::DBD:: modules (or no?  it doesn't anymore?).
670
671 sql_alter_table() also has database-specific foo that ought to be abstracted
672 into the DBIx::DBSchema::DBD:: modules.
673
674 sql_create_table() may change or destroy the object's data.  If you need to use
675 the object after sql_create_table, make a copy beforehand.
676
677 Some of the logic in new_odbc might be better abstracted into Column.pm etc.
678
679 sql_alter_table ought to drop columns not in $new
680
681 Add methods to get and set specific indices, by name? (like column COLUMN_NAME)
682
683 indices method should be a setter, not just a getter?
684
685 =head1 SEE ALSO
686
687 L<DBIx::DBSchema>, L<DBIx::DBSchema::ColGroup::Unique>,
688 L<DBIx::DBSchema::ColGroup::Index>, L<DBIx::DBSchema::Column>, L<DBI>
689
690 =cut
691
692 1;
693