Add IF EXISTS to DROP INDEX (except under MySQL)
[DBIx-DBSchema.git] / DBSchema / Table.pm
index 8d047de..0ef679a 100644 (file)
@@ -1,17 +1,14 @@
 package DBIx::DBSchema::Table;
 
 use strict;
 package DBIx::DBSchema::Table;
 
 use strict;
-use vars qw($VERSION $DEBUG %create_params);
 use Carp;
 use Carp;
-#use Exporter;
 use DBIx::DBSchema::_util qw(_load_driver _dbh _parse_opt);
 use DBIx::DBSchema::Column 0.14;
 use DBIx::DBSchema::Index;
 use DBIx::DBSchema::_util qw(_load_driver _dbh _parse_opt);
 use DBIx::DBSchema::Column 0.14;
 use DBIx::DBSchema::Index;
-use DBIx::DBSchema::ColGroup::Unique;
-use DBIx::DBSchema::ColGroup::Index;
+use DBIx::DBSchema::ForeignKey 0.13;
 
 
-$VERSION = '0.08';
-$DEBUG = 0;
+our $VERSION = '0.12';
+our $DEBUG = 0;
 
 =head1 NAME
 
 
 =head1 NAME
 
@@ -24,12 +21,13 @@ DBIx::DBSchema::Table - Table objects
   #new style (preferred), pass a hashref of parameters
   $table = new DBIx::DBSchema::Table (
     {
   #new style (preferred), pass a hashref of parameters
   $table = new DBIx::DBSchema::Table (
     {
-      name        => "table_name",
-      primary_key => "primary_key",
-      columns     => \@dbix_dbschema_column_objects,
+      name         => "table_name",
+      primary_key  => "primary_key",
+      columns      => \@dbix_dbschema_column_objects,
       #deprecated# unique      => $dbix_dbschema_colgroup_unique_object,
       #deprecated# 'index'     => $dbix_dbschema_colgroup_index_object,
       #deprecated# unique      => $dbix_dbschema_colgroup_unique_object,
       #deprecated# 'index'     => $dbix_dbschema_colgroup_index_object,
-      indices     => \@dbix_dbschema_index_objects,
+      indices      => \@dbix_dbschema_index_objects,
+      foreign_keys => \@dbix_dbschema_foreign_key_objects,
     }
   );
 
     }
   );
 
@@ -92,24 +90,24 @@ hash reference of named parameters.
     columns       => COLUMNS,
     indices       => INDICES,
     local_options => OPTIONS,
     columns       => COLUMNS,
     indices       => INDICES,
     local_options => OPTIONS,
-    #deprecated# unique => UNIQUE,
-    #deprecated# index  => INDEX,
   }
 
   }
 
-TABLE_NAME is the name of the table.  PRIMARY_KEY is the primary key (may be
-empty).  COLUMNS is a reference to an array of DBIx::DBSchema::Column objects
-(see L<DBIx::DBSchema::Column>).  INDICES is a reference to an array of 
-DBIx::DBSchema::Index objects (see L<DBIx::DBSchema::Index>), or a hash
-reference of index names (keys) and DBIx::DBSchema::Index objects (values).
-OPTIONS is a scalar of database-specific table options, such as "WITHOUT OIDS"
-for Pg or "TYPE=InnoDB" for mysql.
+TABLE_NAME is the name of the table.
+
+PRIMARY_KEY is the primary key (may be empty).
+
+COLUMNS is a reference to an array of DBIx::DBSchema::Column objects
+(see L<DBIx::DBSchema::Column>).
+
+INDICES is a reference to an array of DBIx::DBSchema::Index objects
+(see L<DBIx::DBSchema::Index>), or a hash reference of index names (keys) and
+DBIx::DBSchema::Index objects (values).
 
 
-Deprecated options:
+FOREIGN_KEYS is a references to an array of DBIx::DBSchema::ForeignKey objects
+(see L<DBIx::DBSchema::ForeignKey>).
 
 
-UNIQUE was a DBIx::DBSchema::ColGroup::Unique object (see
-L<DBIx::DBSchema::ColGroup::Unique>).  INDEX was a
-DBIx::DBSchema::ColGroup::Index object (see
-L<DBIx::DBSchema::ColGroup::Index>).
+OPTIONS is a scalar of database-specific table options, such as "WITHOUT OIDS"
+for Pg or "TYPE=InnoDB" for mysql.
 
 =cut
 
 
 =cut
 
@@ -127,6 +125,8 @@ sub new {
     $self->{indices} = { map { $_->name, $_ } @{$self->{indices}} }
        if ref($self->{indices}) eq 'ARRAY';
 
     $self->{indices} = { map { $_->name, $_ } @{$self->{indices}} }
        if ref($self->{indices}) eq 'ARRAY';
 
+    $self->{foreign_keys} ||= [];
+
   } else {
 
     carp "Old-style $class creation without named parameters is deprecated!";
   } else {
 
     carp "Old-style $class creation without named parameters is deprecated!";
@@ -145,6 +145,7 @@ sub new {
       'index'        => $index,
       'columns'      => \%columns,
       'column_order' => \@column_order,
       'index'        => $index,
       'columns'      => \%columns,
       'column_order' => \@column_order,
+      'foreign_keys' => [],
     };
 
   }
     };
 
   }
@@ -175,7 +176,7 @@ have to have ODBC installed or connect to the database via ODBC.
 
 =cut
 
 
 =cut
 
-%create_params = (
+our %create_params = (
 #  undef             => sub { '' },
   ''                => sub { '' },
   'max length'      => sub { $_[0]->{PRECISION}->[$_[1]]; },
 #  undef             => sub { '' },
   ''                => sub { '' },
   'max length'      => sub { $_[0]->{PRECISION}->[$_[1]]; },
@@ -235,19 +236,7 @@ sub new_odbc {
     
     ],
 
     
     ],
 
-    #old-style indices
-    #DBIx::DBSchema::ColGroup::Unique->new(
-    #  $driver
-    #   ? [values %{eval "DBIx::DBSchema::DBD::$driver->unique(\$dbh, \$name)"}]
-    #   : []
-    #),
-    #DBIx::DBSchema::ColGroup::Index->new(
-    #  $driver
-    #  ? [ values %{eval "DBIx::DBSchema::DBD::$driver->index(\$dbh, \$name)"} ]
-    #  : []
-    #),
-
-    #new-style indices
+    #indices
     'indices' => { map { my $indexname = $_;
                          $indexname =>
                            DBIx::DBSchema::Index->new($indices_hr->{$indexname})
     'indices' => { map { my $indexname = $_;
                          $indexname =>
                            DBIx::DBSchema::Index->new($indices_hr->{$indexname})
@@ -272,6 +261,9 @@ sub new_native {
   my( $proto, $dbh, $name) = @_;
   my $driver = _load_driver($dbh);
 
   my( $proto, $dbh, $name) = @_;
   my $driver = _load_driver($dbh);
 
+  my $primary_key =
+    scalar(eval "DBIx::DBSchema::DBD::$driver->primary_key(\$dbh, \$name)"),
+
   my $indices_hr =
   ( $driver
       ? eval "DBIx::DBSchema::DBD::$driver->indices(\$dbh, \$name)"
   my $indices_hr =
   ( $driver
       ? eval "DBIx::DBSchema::DBD::$driver->indices(\$dbh, \$name)"
@@ -279,23 +271,14 @@ sub new_native {
   );
 
   $proto->new({
   );
 
   $proto->new({
-    'name'        => $name,
-    'primary_key' => scalar(eval "DBIx::DBSchema::DBD::$driver->primary_key(\$dbh, \$name)"),
-    'columns'     => [
-    
+    'name'         => $name,
+    'primary_key'  => $primary_key,
+
+    'columns'      => [
       map DBIx::DBSchema::Column->new( @{$_} ),
           eval "DBIx::DBSchema::DBD::$driver->columns(\$dbh, \$name)"
     ],
 
       map DBIx::DBSchema::Column->new( @{$_} ),
           eval "DBIx::DBSchema::DBD::$driver->columns(\$dbh, \$name)"
     ],
 
-    #old-style indices
-    #DBIx::DBSchema::ColGroup::Unique->new(
-    #  [ values %{eval "DBIx::DBSchema::DBD::$driver->unique(\$dbh, \$name)"} ]
-    #),
-    #DBIx::DBSchema::ColGroup::Index->new(
-    #  [ values %{eval "DBIx::DBSchema::DBD::$driver->index(\$dbh, \$name)"} ]
-    #),
-    
-    #new-style indices
     'indices' => { map { my $indexname = $_;
                          $indexname =>
                            DBIx::DBSchema::Index->new($indices_hr->{$indexname})
     'indices' => { map { my $indexname = $_;
                          $indexname =>
                            DBIx::DBSchema::Index->new($indices_hr->{$indexname})
@@ -303,6 +286,12 @@ sub new_native {
                        keys %$indices_hr
                  },
 
                        keys %$indices_hr
                  },
 
+    'foreign_keys' => [
+      map DBIx::DBSchema::ForeignKey->new( $_ ),
+          eval "DBIx::DBSchema::DBD::$driver->constraints(\$dbh, \$name)"
+    ],
+
+
   });
 }
 
   });
 }
 
@@ -385,66 +374,6 @@ sub primary_key {
   }
 }
 
   }
 }
 
-=item unique [ UNIQUE ]
-
-This method is deprecated and included for backwards-compatibility only.
-See L</indices> for the current method to access unique and non-unique index
-objects.
-
-Returns or sets the DBIx::DBSchema::ColGroup::Unique object.
-
-=cut
-
-sub unique {
-    my $self = shift;
-
-    carp ref($self) . "->unique method is deprecated; see ->indices";
-    #croak ref($self). "->unique method is deprecated; see ->indices";
-
-    $self->_unique(@_);
-}
-
-sub _unique {
-
-  my ($self,$value)=@_;
-
-  if ( defined($value) ) {
-    $self->{unique} = $value;
-  } else {
-    $self->{unique};
-  }
-}
-
-=item index [ INDEX ]
-
-This method is deprecated and included for backwards-compatibility only.
-See L</indices> for the current method to access unique and non-unique index
-objects.
-
-Returns or sets the DBIx::DBSchema::ColGroup::Index object.
-
-=cut
-
-sub index { 
-  my $self = shift;
-
-  carp ref($self). "->index method is deprecated; see ->indices";
-  #croak ref($self). "->index method is deprecated; see ->indices";
-
-  $self->_index(@_);
-}
-
-
-sub _index {
-  my($self,$value)=@_;
-
-  if ( defined($value) ) {
-    $self->{'index'} = $value;
-  } else {
-    $self->{'index'};
-  }
-}
-
 =item columns
 
 Returns a list consisting of the names of all columns.
 =item columns
 
 Returns a list consisting of the names of all columns.
@@ -470,7 +399,7 @@ sub column {
   $self->{'columns'}->{$column};
 }
 
   $self->{'columns'}->{$column};
 }
 
-=item indices COLUMN_NAME
+=item indices
 
 Returns a list of key-value pairs suitable for assigning to a hash.  Keys are
 index names, and values are index objects (see L<DBIx::DBSchema::Index>).
 
 Returns a list of key-value pairs suitable for assigning to a hash.  Keys are
 index names, and values are index objects (see L<DBIx::DBSchema::Index>).
@@ -506,9 +435,6 @@ sub unique_singles {
 
 Returns a list of SQL statments to create this table.
 
 
 Returns a list of SQL statments to create this table.
 
-Optionally, the data source can be specified by passing an open DBI database
-handle, or by passing the DBI data source name, username and password.  
-
 The data source can be specified by passing an open DBI database handle, or by
 passing the DBI data source name, username and password.  
 
 The data source can be specified by passing an open DBI database handle, or by
 passing the DBI data source name, username and password.  
 
@@ -546,6 +472,8 @@ sub sql_create_table {
   push @columns, "PRIMARY KEY (". $self->primary_key. ")"
     if $self->primary_key && ! grep /PRIMARY KEY/i, @columns;
 
   push @columns, "PRIMARY KEY (". $self->primary_key. ")"
     if $self->primary_key && ! grep /PRIMARY KEY/i, @columns;
 
+#  push @columns, $self->foreign_keys_sql;
+
   my $indexnum = 1;
 
   my @r = (
   my $indexnum = 1;
 
   my @r = (
@@ -553,33 +481,6 @@ sub sql_create_table {
     $self->local_options
   );
 
     $self->local_options
   );
 
-  if ( $self->_unique ) {
-
-    warn "WARNING: DBIx::DBSchema::Table object for ". $self->name.
-         " table has deprecated (non-named) unique indices\n";
-
-    push @r, map {
-                   #my($index) = $self->name. "__". $_ . "_idx";
-                   #$index =~ s/,\s*/_/g;
-                   my $index = $self->name. $indexnum++;
-                   "CREATE UNIQUE INDEX $index ON ". $self->name. " ($_)\n"
-                 } $self->unique->sql_list;
-
-  }
-
-  if ( $self->_index ) {
-
-    warn "WARNING: DBIx::DBSchema::Table object for ". $self->name.
-         " table has deprecated (non-named) indices\n";
-
-    push @r, map {
-                   #my($index) = $self->name. "__". $_ . "_idx";
-                   #$index =~ s/,\s*/_/g;
-                   my $index = $self->name. $indexnum++;
-                   "CREATE INDEX $index ON ". $self->name. " ($_)\n"
-                 } $self->index->sql_list;
-  }
-
   my %indices = $self->indices;
   #push @r, map { $indices{$_}->sql_create_index( $self->name ) } keys %indices;
   foreach my $index ( keys %indices ) {
   my %indices = $self->indices;
   #push @r, map { $indices{$_}->sql_create_index( $self->name ) } keys %indices;
   foreach my $index ( keys %indices ) {
@@ -590,6 +491,32 @@ sub sql_create_table {
   @r;
 }
 
   @r;
 }
 
+=item sql_add_constraints [ DATABASE_HANDLE | DATA_SOURCE [ USERNAME PASSWORD [ ATTR ] ] ]
+
+Returns a list of SQL statments to add constraints (foreign keys) to this table.
+
+The data source can be specified by passing an open DBI database handle, or by
+passing the DBI data source name, username and password.  
+
+Although the username and password are optional, it is best to call this method
+with a database handle or data source including a valid username and password -
+a DBI connection will be opened and the quoting and type mapping will be more
+reliable.
+
+If passed a DBI data source (or handle) such as `DBI:mysql:database', will use
+MySQL- or PostgreSQL-specific syntax.  Non-standard syntax for other engines
+(if applicable) may also be supported in the future.
+
+=cut
+
+sub sql_add_constraints {
+  my $self = shift;
+  my @fks = $self->foreign_keys_sql or return ();
+  (
+    'ALTER TABLE '. $self->name. ' '. join(",\n  ", map "ADD $_", @fks) 
+  );
+}
+
 =item sql_alter_table PROTOTYPE_TABLE, [ DATABASE_HANDLE | DATA_SOURCE [ USERNAME PASSWORD [ ATTR ] ] ]
 
 Returns a list of SQL statements to alter this table so that it is identical
 =item sql_alter_table PROTOTYPE_TABLE, [ DATABASE_HANDLE | DATA_SOURCE [ USERNAME PASSWORD [ ATTR ] ] ]
 
 Returns a list of SQL statements to alter this table so that it is identical
@@ -623,6 +550,7 @@ sub sql_alter_table {
 
   my $table = $self->name;
 
 
   my $table = $self->name;
 
+  my @at = ();
   my @r = ();
   my @r_later = ();
   my $tempnum = 1;
   my @r = ();
   my @r_later = ();
   my $tempnum = 1;
@@ -634,17 +562,22 @@ sub sql_alter_table {
   foreach my $column ( $new->columns ) {
 
     if ( $self->column($column) )  {
   foreach my $column ( $new->columns ) {
 
     if ( $self->column($column) )  {
-
       warn "  $table.$column exists\n" if $DEBUG > 1;
       warn "  $table.$column exists\n" if $DEBUG > 1;
-      push @r, $self->column($column)->sql_alter_column( $new->column($column),
-                                                         $dbh,
-                                                         $opt,
-                                                       );
+
+      my ($alter_table, $sql) = 
+        $self->column($column)->sql_alter_column( $new->column($column),
+                                                  $dbh,
+                                                  $opt,
+                                                );
+      push @at, @$alter_table;
+      push @r, @$sql;
 
     } else {
 
     } else {
-  
       warn "column $table.$column does not exist.\n" if $DEBUG > 1;
       warn "column $table.$column does not exist.\n" if $DEBUG > 1;
-      push @r, $new->column($column)->sql_add_column( $dbh );
+
+      my ($alter_table, $sql) = $new->column($column)->sql_add_column( $dbh );
+      push @at, @$alter_table;
+      push @r, @$sql;
   
     }
   
   
     }
   
@@ -698,8 +631,8 @@ sub sql_alter_table {
     warn "removing obsolete index $table.$old ON ( ".
          $old_indices{$old}->columns_sql. " )\n"
       if $DEBUG > 1;
     warn "removing obsolete index $table.$old ON ( ".
          $old_indices{$old}->columns_sql. " )\n"
       if $DEBUG > 1;
-    push @r, "DROP INDEX $old".
-             ( $driver eq 'mysql' ? " ON $table" : '');
+    push @r, 'DROP INDEX '.  ( $driver ne 'mysql' ? ' IF EXISTS ' : '').
+             " $old ".       ( $driver eq 'mysql' ? " ON $table " : '');
   }
 
   foreach my $new ( keys %new_indices ) {
   }
 
   foreach my $new ( keys %new_indices ) {
@@ -715,14 +648,16 @@ sub sql_alter_table {
 
     warn "column $table.$column should be dropped.\n" if $DEBUG;
 
 
     warn "column $table.$column should be dropped.\n" if $DEBUG;
 
-    push @r, $self->column($column)->sql_drop_column( $dbh );
+    push @at, $self->column($column)->sql_drop_column( $dbh );
 
   }
 
   }
-  
+
   ###
   # return the statements
   ###
   ###
   # return the statements
   ###
-  
+
+  unshift @r, "ALTER TABLE $table ". join(', ', @at) if @at;
+
   push @r, @r_later;
 
   warn join('', map "$_\n", @r)
   push @r, @r_later;
 
   warn join('', map "$_\n", @r)
@@ -732,6 +667,64 @@ sub sql_alter_table {
 
 }
 
 
 }
 
+=item sql_alter_constraints PROTOTYPE_TABLE, [ DATABASE_HANDLE | DATA_SOURCE [ USERNAME PASSWORD [ ATTR ] ] ]
+
+Returns a list of SQL statements to alter this table's constraints (foreign
+keys) so that they are identical to the provided table, also a
+DBIx::DBSchema::Table object.
+
+The data source can be specified by passing an open DBI database handle, or by
+passing the DBI data source name, username and password.  
+
+Although the username and password are optional, it is best to call this method
+with a database handle or data source including a valid username and password -
+a DBI connection will be opened and used to check the database version as well
+as for more reliable quoting and type mapping.  Note that the database
+connection will be used passively, B<not> to actually run the CREATE
+statements.
+
+If passed a DBI data source (or handle) such as `DBI:mysql:database' or
+`DBI:Pg:dbname=database', will use syntax specific to that database engine.
+Currently supported databases are MySQL and PostgreSQL.
+
+If not passed a data source (or handle), or if there is no driver for the
+specified database, will attempt to use generic SQL syntax.
+
+=cut
+
+sub sql_alter_constraints {
+  my($self, $opt, $new, $dbh) = ( shift, _parse_opt(\@_), shift, _dbh(@_) );
+
+  my $driver = _load_driver($dbh);
+
+  my $table = $self->name;
+
+  my @at = ();
+
+  # foreign keys (add)
+  foreach my $foreign_key ( $new->foreign_keys ) {
+
+    next if grep $foreign_key->cmp($_), $self->foreign_keys;
+
+    push @at, 'ADD '. $foreign_key->sql_foreign_key;
+  }
+
+  #foreign keys (drop)
+  foreach my $foreign_key ( $self->foreign_keys ) {
+
+    next if grep $foreign_key->cmp($_), $new->foreign_keys;
+    next unless $foreign_key->constraint;
+
+    push @at, 'DROP CONSTRAINT '. $foreign_key->constraint;
+  }
+
+  return () unless @at;
+  (
+    'ALTER TABLE '. $self->name. ' '. join(",\n  ", @at) 
+  );
+
+}
+
 sub sql_drop_table {
   my( $self, $dbh ) = ( shift, _dbh(@_) );
 
 sub sql_drop_table {
   my( $self, $dbh ) = ( shift, _dbh(@_) );
 
@@ -740,6 +733,29 @@ sub sql_drop_table {
   ("DROP TABLE $name");
 }
 
   ("DROP TABLE $name");
 }
 
+=item foreign_keys_sql
+
+=cut
+
+sub foreign_keys_sql {
+  my $self = shift;
+  map $_->sql_foreign_key, $self->foreign_keys;
+}
+
+=item foreign_keys
+
+Returns a list of foreign keys (DBIx::DBSchema::ForeignKey objects).
+
+=cut
+
+sub foreign_keys {
+  my $self = shift;
+  exists( $self->{'foreign_keys'} )
+    ? @{ $self->{'foreign_keys'} }
+    : ();
+}
+
+
 sub _null_sth {
   my($dbh, $table) = @_;
   my $sth = $dbh->prepare("SELECT * FROM $table WHERE 1=0")
 sub _null_sth {
   my($dbh, $table) = @_;
   my $sth = $dbh->prepare("SELECT * FROM $table WHERE 1=0")
@@ -761,7 +777,7 @@ with no indices.
 
 Copyright (c) 2000-2007 Ivan Kohler
 Copyright (c) 2000 Mail Abuse Prevention System LLC
 
 Copyright (c) 2000-2007 Ivan Kohler
 Copyright (c) 2000 Mail Abuse Prevention System LLC
-Copyright (c) 2007-2010 Freeside Internet Services, Inc.
+Copyright (c) 2007-2013 Freeside Internet Services, Inc.
 All rights reserved.
 This program is free software; you can redistribute it and/or modify it under
 the same terms as Perl itself.
 All rights reserved.
 This program is free software; you can redistribute it and/or modify it under
 the same terms as Perl itself.
@@ -785,8 +801,8 @@ indices method should be a setter, not just a getter?
 
 =head1 SEE ALSO
 
 
 =head1 SEE ALSO
 
-L<DBIx::DBSchema>, L<DBIx::DBSchema::ColGroup::Unique>,
-L<DBIx::DBSchema::ColGroup::Index>, L<DBIx::DBSchema::Column>, L<DBI>
+L<DBIx::DBSchema>, L<DBIx::DBSchema::Column>, L<DBI>,
+L<DBIx::DBSchema::Index>, L<DBIx::DBSchema::FoeignKey>
 
 =cut
 
 
 =cut