Patch from ISHIGAKI@cpan.org to suppress unnecessary warnings about undefined local_o...
[DBIx-DBSchema.git] / DBSchema / Table.pm
index 57c7c5c..225bcbb 100644 (file)
@@ -1,15 +1,17 @@
 package DBIx::DBSchema::Table;
 
 use strict;
 package DBIx::DBSchema::Table;
 
 use strict;
-use vars qw(@ISA %create_params);
-#use Carp;
-use Exporter;
-use DBIx::DBSchema::Column;
+use vars qw($VERSION $DEBUG %create_params);
+use Carp;
+#use Exporter;
+use DBIx::DBSchema::_util qw(_load_driver _dbh);
+use DBIx::DBSchema::Column 0.07;
+use DBIx::DBSchema::Index;
 use DBIx::DBSchema::ColGroup::Unique;
 use DBIx::DBSchema::ColGroup::Index;
 
 use DBIx::DBSchema::ColGroup::Unique;
 use DBIx::DBSchema::ColGroup::Index;
 
-#@ISA = qw(Exporter);
-@ISA = qw();
+$VERSION = '0.07';
+$DEBUG = 0;
 
 =head1 NAME
 
 
 =head1 NAME
 
@@ -19,6 +21,19 @@ DBIx::DBSchema::Table - Table objects
 
   use DBIx::DBSchema::Table;
 
 
   use 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,
+      #deprecated# unique      => $dbix_dbschema_colgroup_unique_object,
+      #deprecated# 'index'     => $dbix_dbschema_colgroup_index_object,
+      indices     => \@dbix_dbschema_index_objects,
+    }
+  );
+
+  #old style (VERY deprecated)
   $table = new DBIx::DBSchema::Table (
     "table_name",
     "primary_key",
   $table = new DBIx::DBSchema::Table (
     "table_name",
     "primary_key",
@@ -35,22 +50,27 @@ DBIx::DBSchema::Table - Table objects
   $primary_key = $table->primary_key;
   $table->primary_key("primary_key");
 
   $primary_key = $table->primary_key;
   $table->primary_key("primary_key");
 
-  $dbix_dbschema_colgroup_unique_object = $table->unique;
-  $table->unique( $dbix_dbschema__colgroup_unique_object );
+  #deprecated# $dbix_dbschema_colgroup_unique_object = $table->unique;
+  #deprecated# $table->unique( $dbix_dbschema__colgroup_unique_object );
 
 
-  $dbix_dbschema_colgroup_index_object = $table->index;
-  $table->index( $dbix_dbschema_colgroup_index_object );
+  #deprecated# $dbix_dbschema_colgroup_index_object = $table->index;
+  #deprecated# $table->index( $dbix_dbschema_colgroup_index_object );
+
+  %indices = $table->indices;
+  $dbix_dbschema_index_object = $indices{'index_name'};
+  @all_index_names = keys %indices;
+  @all_dbix_dbschema_index_objects = values %indices;
 
   @column_names = $table->columns;
 
   $dbix_dbschema_column_object = $table->column("column");
 
   #preferred
 
   @column_names = $table->columns;
 
   $dbix_dbschema_column_object = $table->column("column");
 
   #preferred
-  @sql_statements = $table->sql_create_table $dbh;
-  @sql_statements = $table->sql_create_table $datasrc, $username, $password;
+  @sql_statements = $table->sql_create_table( $dbh );
+  @sql_statements = $table->sql_create_table( $datasrc, $username, $password );
 
   #possible problems
 
   #possible problems
-  @sql_statements = $table->sql_create_table $datasrc;
+  @sql_statements = $table->sql_create_table( $datasrc );
   @sql_statements = $table->sql_create_table;
 
 =head1 DESCRIPTION
   @sql_statements = $table->sql_create_table;
 
 =head1 DESCRIPTION
@@ -61,39 +81,82 @@ DBIx::DBSchema::Table objects represent a single database table.
 
 =over 4
 
 
 =over 4
 
-=item new [ TABLE_NAME [ , PRIMARY_KEY [ , UNIQUE [ , INDEX [ , COLUMN... ] ] ] ] ]
+=item new HASHREF
+
+Creates a new DBIx::DBSchema::Table object.  The preferred usage is to pass a
+hash reference of named parameters.
 
 
-Creates a new DBIx::DBSchema::Table object.  TABLE_NAME is the name of the
-table.  PRIMARY_KEY is the primary key (may be empty).  UNIQUE is a
-DBIx::DBSchema::ColGroup::Unique object (see
-L<DBIx::DBSchema::ColGroup::Unique>).  INDEX is a
+  {
+    name          => TABLE_NAME,
+    primary_key   => PRIMARY_KEY,
+    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.
+
+Deprecated options:
+
+UNIQUE was a DBIx::DBSchema::ColGroup::Unique object (see
+L<DBIx::DBSchema::ColGroup::Unique>).  INDEX was a
 DBIx::DBSchema::ColGroup::Index object (see
 DBIx::DBSchema::ColGroup::Index object (see
-L<DBIx::DBSchema::ColGroup::Index>).  The rest of the arguments should be
-DBIx::DBSchema::Column objects (see L<DBIx::DBSchema::Column>).
+L<DBIx::DBSchema::ColGroup::Index>).
 
 =cut
 
 sub new {
 
 =cut
 
 sub new {
-  my($proto,$name,$primary_key,$unique,$index,@columns)=@_;
+  my $proto = shift;
+  my $class = ref($proto) || $proto;
 
 
-  my(%columns) = map { $_->name, $_ } @columns;
-  my(@column_order) = map { $_->name } @columns;
+  my $self;
+  if ( ref($_[0]) ) {
+
+    $self = shift;
+    $self->{column_order} = [ map { $_->name } @{$self->{columns}} ];
+    $self->{columns} = { map { $_->name, $_ } @{$self->{columns}} };
+
+    $self->{indices} = { map { $_->name, $_ } @{$self->{indices}} }
+       if ref($self->{indices}) eq 'ARRAY';
+
+  } else {
+
+    carp "Old-style $class creation without named parameters is deprecated!";
+    #croak "FATAL: old-style $class creation no longer supported;".
+    #      " use named parameters";
+
+    my($name,$primary_key,$unique,$index,@columns) = @_;
+
+    my %columns = map { $_->name, $_ } @columns;
+    my @column_order = map { $_->name } @columns;
+
+    $self = {
+      'name'         => $name,
+      'primary_key'  => $primary_key,
+      'unique'       => $unique,
+      'index'        => $index,
+      'columns'      => \%columns,
+      'column_order' => \@column_order,
+    };
+
+  }
 
   #check $primary_key, $unique and $index to make sure they are $columns ?
   # (and sanity check?)
 
 
   #check $primary_key, $unique and $index to make sure they are $columns ?
   # (and sanity check?)
 
-  my $class = ref($proto) || $proto;
-  my $self = {
-    'name'         => $name,
-    'primary_key'  => $primary_key,
-    'unique'       => $unique,
-    'index'        => $index,
-    'columns'      => \%columns,
-    'column_order' => \@column_order,
-  };
-
   bless ($self, $class);
 
   bless ($self, $class);
 
+  $_->table_obj($self) foreach values %{ $self->{columns} };
+
+  $self;
 }
 
 =item new_odbc DATABASE_HANDLE TABLE_NAME
 }
 
 =item new_odbc DATABASE_HANDLE TABLE_NAME
@@ -107,6 +170,9 @@ primary key and (unique) index information will only be imported from databases
 with DBIx::DBSchema::DBD drivers (currently MySQL and PostgreSQL), import of
 column names and attributes *should* work for any database.
 
 with DBIx::DBSchema::DBD drivers (currently MySQL and PostgreSQL), import of
 column names and attributes *should* work for any database.
 
+Note: the _odbc refers to the column types used and nothing else - you do not
+have to have ODBC installed or connect to the database via ODBC.
+
 =cut
 
 %create_params = (
 =cut
 
 %create_params = (
@@ -119,38 +185,77 @@ column names and attributes *should* work for any database.
 
 sub new_odbc {
   my( $proto, $dbh, $name) = @_;
 
 sub new_odbc {
   my( $proto, $dbh, $name) = @_;
-  my $driver = DBIx::DBSchema::_load_driver($dbh);
+
+  my $driver = _load_driver($dbh);
   my $sth = _null_sth($dbh, $name);
   my $sthpos = 0;
   my $sth = _null_sth($dbh, $name);
   my $sthpos = 0;
-  $proto->new (
-    $name,
-    scalar(eval "DBIx::DBSchema::DBD::$driver->primary_key(\$dbh, \$name)"),
-    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)"} ]
-      : []
-    ),
-    map { 
-      my $type_info = scalar($dbh->type_info($sth->{TYPE}->[$sthpos]))
-        or die "DBI::type_info ". $dbh->{Driver}->{Name}. " driver ".
-               "returned no results for type ".  $sth->{TYPE}->[$sthpos];
-      new DBIx::DBSchema::Column
-          $_,
-          $type_info->{'TYPE_NAME'},
-          #"SQL_". uc($type_info->{'TYPE_NAME'}),
-          $sth->{NULLABLE}->[$sthpos],
-          &{ $create_params{ $type_info->{CREATE_PARAMS} } }( $sth, $sthpos++ ),          $driver && #default
-            ${ [
-              eval "DBIx::DBSchema::DBD::$driver->column(\$dbh, \$name, \$_)"
-            ] }[4]
-          # DB-local
-    } @{$sth->{NAME}}
-  );
+
+  my $indices_hr =
+    ( $driver
+        ? eval "DBIx::DBSchema::DBD::$driver->indices(\$dbh, \$name)"
+        : {}
+    );
+
+  $proto->new({
+    'name'        => $name,
+    'primary_key' => scalar(eval "DBIx::DBSchema::DBD::$driver->primary_key(\$dbh, \$name)"),
+
+    'columns'     => [
+    
+      map { 
+
+            my $col_name = $_;
+
+            my $type_info = scalar($dbh->type_info($sth->{TYPE}->[$sthpos]))
+              or die "DBI::type_info ". $dbh->{Driver}->{Name}. " driver ".
+                     "returned no results for type ".  $sth->{TYPE}->[$sthpos];
+
+            my $length = &{ $create_params{ $type_info->{CREATE_PARAMS} } }
+                          ( $sth, $sthpos++ );
+
+            my $default = '';
+            if ( $driver ) {
+              $default = ${ [
+                eval "DBIx::DBSchema::DBD::$driver->column(\$dbh, \$name, \$_)"
+              ] }[4];
+            }
+
+            DBIx::DBSchema::Column->new({
+                'name'    => $col_name,
+                #'type'    => "SQL_". uc($type_info->{'TYPE_NAME'}),
+                'type'    => $type_info->{'TYPE_NAME'},
+                'null'    => $sth->{NULLABLE}->[$sthpos],
+                'length'  => $length,          
+                'default' => $default,
+                #'local'   => # DB-local
+            });
+
+          }
+          @{$sth->{NAME}}
+    
+    ],
+
+    #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' => { map { my $indexname = $_;
+                         $indexname =>
+                           DBIx::DBSchema::Index->new($indices_hr->{$indexname})
+                       } 
+                       keys %$indices_hr
+                 },
+
+  });
 }
 
 =item new_native DATABASE_HANDLE TABLE_NAME
 }
 
 =item new_native DATABASE_HANDLE TABLE_NAME
@@ -165,20 +270,40 @@ engine (currently, MySQL and PostgreSQL).
 
 sub new_native {
   my( $proto, $dbh, $name) = @_;
 
 sub new_native {
   my( $proto, $dbh, $name) = @_;
-  my $driver = DBIx::DBSchema::_load_driver($dbh);
-  $proto->new (
-    $name,
-    scalar(eval "DBIx::DBSchema::DBD::$driver->primary_key(\$dbh, \$name)"),
-    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)"} ]
-    ),
-    map {
-      DBIx::DBSchema::Column->new( @{$_} )
-    } eval "DBIx::DBSchema::DBD::$driver->columns(\$dbh, \$name)"
+  my $driver = _load_driver($dbh);
+
+  my $indices_hr =
+  ( $driver
+      ? eval "DBIx::DBSchema::DBD::$driver->indices(\$dbh, \$name)"
+      : {}
   );
   );
+
+  $proto->new({
+    'name'        => $name,
+    'primary_key' => scalar(eval "DBIx::DBSchema::DBD::$driver->primary_key(\$dbh, \$name)"),
+    'columns'     => [
+    
+      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})
+                       } 
+                       keys %$indices_hr
+                 },
+
+  });
 }
 
 =item addcolumn COLUMN
 }
 
 =item addcolumn COLUMN
@@ -188,11 +313,27 @@ Adds this DBIx::DBSchema::Column object.
 =cut
 
 sub addcolumn {
 =cut
 
 sub addcolumn {
-  my($self,$column)=@_;
-  ${$self->{'columns'}}{$column->name}=$column; #sanity check?
+  my($self, $column) = @_;
+  $column->table_obj($self);
+  ${$self->{'columns'}}{$column->name} = $column; #sanity check?
   push @{$self->{'column_order'}}, $column->name;
 }
 
   push @{$self->{'column_order'}}, $column->name;
 }
 
+=item delcolumn COLUMN_NAME
+
+Deletes this column.  Returns false if no column of this name was found to
+remove, true otherwise.
+
+=cut
+
+sub delcolumn {
+  my($self,$column) = @_;
+  return 0 unless exists $self->{'columns'}{$column};
+  $self->{'columns'}{$column}->table_obj('');
+  delete $self->{'columns'}{$column};
+  @{$self->{'column_order'}}= grep { $_ ne $column } @{$self->{'column_order'}};  1;
+}
+
 =item name [ TABLE_NAME ]
 
 Returns or sets the table name.
 =item name [ TABLE_NAME ]
 
 Returns or sets the table name.
@@ -208,6 +349,21 @@ sub name {
   }
 }
 
   }
 }
 
+=item local_options [ OPTIONS ]
+
+Returns or sets the database-specific table options string.
+
+=cut
+
+sub local_options {
+  my($self,$value)=@_;
+  if ( defined($value) ) {
+    $self->{local_options} = $value;
+  } else {
+    defined $self->{local_options} ? $self->{local_options} : '';
+  }
+}
+
 =item primary_key [ PRIMARY_KEY ]
 
 Returns or sets the primary key.
 =item primary_key [ PRIMARY_KEY ]
 
 Returns or sets the primary key.
@@ -231,12 +387,27 @@ sub primary_key {
 
 =item unique [ UNIQUE ]
 
 
 =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
 
 Returns or sets the DBIx::DBSchema::ColGroup::Unique object.
 
 =cut
 
-sub unique { 
-  my($self,$value)=@_;
+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 {
   if ( defined($value) ) {
     $self->{unique} = $value;
   } else {
@@ -246,12 +417,27 @@ sub unique {
 
 =item index [ INDEX ]
 
 
 =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 { 
 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)=@_;
   my($self,$value)=@_;
+
   if ( defined($value) ) {
     $self->{'index'} = $value;
   } else {
   if ( defined($value) ) {
     $self->{'index'} = $value;
   } else {
@@ -284,10 +470,45 @@ sub column {
   $self->{'columns'}->{$column};
 }
 
   $self->{'columns'}->{$column};
 }
 
+=item indices COLUMN_NAME
+
+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>).
+
+=cut
+
+sub indices {
+  my $self = shift;
+  exists( $self->{'indices'} )
+    ? %{ $self->{'indices'} }
+    : ();
+}
+
+=item unique_singles
+
+Meet exciting and unique singles using this method!
+
+This method returns a list of column names that are indexed with their own,
+unique, non-compond (that's the "single" part) indices.
+
+=cut
+
+sub unique_singles {
+  my $self = shift;
+  my %indices = $self->indices;
+
+  map { ${ $indices{$_}->columns }[0] }
+      grep { $indices{$_}->unique && scalar(@{$indices{$_}->columns}) == 1 }
+           keys %indices;
+}
+
 =item sql_create_table [ DATABASE_HANDLE | DATA_SOURCE [ USERNAME PASSWORD [ ATTR ] ] ]
 
 Returns a list of SQL statments to create this table.
 
 =item sql_create_table [ DATABASE_HANDLE | DATA_SOURCE [ USERNAME PASSWORD [ ATTR ] ] ]
 
 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.  
 
@@ -303,68 +524,224 @@ MySQL- or PostgreSQL-specific syntax.  Non-standard syntax for other engines
 =cut
 
 sub sql_create_table { 
 =cut
 
 sub sql_create_table { 
-  my($self, $dbh) = (shift, shift);
+  my($self, $dbh) = ( shift, _dbh(@_) );
 
 
-  my $created_dbh = 0;
-  unless ( ref($dbh) || ! @_ ) {
-    $dbh = DBI->connect( $dbh, @_ ) or die $DBI::errstr;
-    my $gratuitous = $DBI::errstr; #surpress superfluous `used only once' error
-    $created_dbh = 1;
-  }
-  #false laziness: nicked from DBSchema::_load_driver
-  my $driver;
-  if ( ref($dbh) ) {
-    $driver = $dbh->{Driver}->{Name};
-  } else {
-    my $discard = $dbh;
-    $discard =~ s/^dbi:(\w*?)(?:\((.*?)\))?://i #nicked from DBI->connect
-                        or '' =~ /()/; # ensure $1 etc are empty if match fails
-    $driver = $1 or die "can't parse data source: $dbh";
-  }
-  #eofalse
+  my $driver = _load_driver($dbh);
 
 
+#should be in the DBD somehwere :/
+#  my $saved_pkey = '';
 #  if ( $driver eq 'Pg' && $self->primary_key ) {
 #    my $pcolumn = $self->column( (
 #      grep { $self->column($_)->name eq $self->primary_key } $self->columns
 #    )[0] );
 #  if ( $driver eq 'Pg' && $self->primary_key ) {
 #    my $pcolumn = $self->column( (
 #      grep { $self->column($_)->name eq $self->primary_key } $self->columns
 #    )[0] );
-#    $pcolumn->type('serial') if lc($pcolumn->type) eq 'integer';
-##    $pcolumn->local( $pcolumn->local. ' PRIMARY KEY' );
-##    $self->primary_key('');
-#    #prolly shoudl change it back afterwords :/
+##AUTO-INCREMENT#    $pcolumn->type('serial') if lc($pcolumn->type) eq 'integer';
+#    $pcolumn->local( $pcolumn->local. ' PRIMARY KEY' );
+#    #my $saved_pkey = $self->primary_key;
+#    #$self->primary_key('');
+#    #change it back afterwords :/
 #  }
 
 #  }
 
-  my(@columns)=map { $self->column($_)->line($dbh) } $self->columns;
+  my @columns = map { $self->column($_)->line($dbh) } $self->columns;
 
   push @columns, "PRIMARY KEY (". $self->primary_key. ")"
 
   push @columns, "PRIMARY KEY (". $self->primary_key. ")"
-    if $self->primary_key && $driver ne 'Pg';
-
-  if ( $driver eq 'mysql' ) { #yucky mysql hack
-    push @columns, map "UNIQUE ($_)", $self->unique->sql_list;
-    push @columns, map "INDEX ($_)", $self->index->sql_list;
-  }
+    if $self->primary_key && ! grep /PRIMARY KEY/i, @columns;
 
   my $indexnum = 1;
 
   my @r = (
 
   my $indexnum = 1;
 
   my @r = (
-    "CREATE TABLE ". $self->name. " (\n  ". join(",\n  ", @columns). "\n)\n",
-    ( 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 ),
-    ( 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 ),
-  );  
-  $dbh->disconnect if $created_dbh;
+    "CREATE TABLE ". $self->name. " (\n  ". join(",\n  ", @columns). "\n)\n".
+    $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 ) {
+    push @r, $indices{$index}->sql_create_index( $self->name );
+  }
+
+  #$self->primary_key($saved_pkey) if $saved_pkey;
   @r;
 }
 
   @r;
 }
 
-#
+=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
+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
+
+#gosh, false laziness w/DBSchema::sql_update_schema
+
+sub sql_alter_table {
+  my( $self, $new, $dbh ) = ( shift, shift, _dbh(@_) );
+
+  my $driver = _load_driver($dbh);
+
+  my $table = $self->name;
+
+  my @r = ();
+  my @r_later = ();
+  my $tempnum = 1;
+
+  ###
+  # columns (add/alter)
+  ###
+
+  foreach my $column ( $new->columns ) {
+
+    if ( $self->column($column) )  {
+
+      warn "  $table.$column exists\n" if $DEBUG > 1;
+
+      push @r,
+        $self->column($column)->sql_alter_column( $new->column($column), $dbh );
+
+    } else {
+  
+      warn "column $table.$column does not exist.\n" if $DEBUG > 1;
+
+      push @r,
+        $new->column($column)->sql_add_column( $dbh );
+  
+    }
+  
+  }
+
+  #should eventually drop columns not in $new...
+  
+  ###
+  # indices
+  ###
+
+  my %old_indices = $self->indices;
+  my %new_indices = $new->indices;
+
+  foreach my $old ( keys %old_indices ) {
+
+    if ( exists( $new_indices{$old} )
+         && $old_indices{$old}->cmp( $new_indices{$old} )
+       )
+    {
+      warn "index $table.$old is identical; not changing\n" if $DEBUG > 1;
+      delete $old_indices{$old};
+      delete $new_indices{$old};
+
+    } elsif ( $driver eq 'Pg' and $dbh->{'pg_server_version'} >= 80000 ) {
+
+      my @same = grep { $old_indices{$old}->cmp_noname( $new_indices{$_} ) }
+                      keys %new_indices;
+
+      if ( @same ) {
+
+        #warn if there's more than one?
+        my $same = shift @same;
+
+        warn "index $table.$old is identical to $same; renaming\n"
+          if $DEBUG > 1;
+
+        my $temp = 'dbs_temp'.$tempnum++;
+
+        push @r, "ALTER INDEX $old RENAME TO $temp";
+        push @r_later, "ALTER INDEX $temp RENAME TO $same";
+
+        delete $old_indices{$old};
+        delete $new_indices{$same};
+
+      }
+
+    }
+
+  }
+
+  foreach my $old ( keys %old_indices ) {
+    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" : '');
+  }
+
+  foreach my $new ( keys %new_indices ) {
+    warn "creating new index $table.$new\n" if $DEBUG > 1;
+    push @r, $new_indices{$new}->sql_create_index($table);
+  }
+
+  ###
+  # columns (drop)
+  ###
+
+  foreach my $column ( grep !$new->column($_), $self->columns ) {
+
+    warn "column $table.$column should be dropped.\n" if $DEBUG;
+
+    push @r, $self->column($column)->sql_drop_column( $dbh );
+
+  }
+  
+  ###
+  # return the statements
+  ###
+  
+  push @r, @r_later;
+
+  warn join('', map "$_\n", @r)
+    if $DEBUG && @r;
+
+  @r;
+
+}
+
+sub sql_drop_table {
+  my( $self, $dbh ) = ( shift, _dbh(@_) );
+
+  my $name = $self->name;
+
+  ("DROP TABLE $name");
+}
 
 sub _null_sth {
   my($dbh, $table) = @_;
 
 sub _null_sth {
   my($dbh, $table) = @_;
@@ -380,10 +757,14 @@ sub _null_sth {
 
 Ivan Kohler <ivan-dbix-dbschema@420.am>
 
 
 Ivan Kohler <ivan-dbix-dbschema@420.am>
 
+Thanks to Mark Ethan Trostler <mark@zzo.com> for a patch to allow tables
+with no indices.
+
 =head1 COPYRIGHT
 
 =head1 COPYRIGHT
 
-Copyright (c) 2000 Ivan Kohler
+Copyright (c) 2000-2007 Ivan Kohler
 Copyright (c) 2000 Mail Abuse Prevention System LLC
 Copyright (c) 2000 Mail Abuse Prevention System LLC
+Copyright (c) 2007 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.
@@ -391,13 +772,22 @@ the same terms as Perl itself.
 =head1 BUGS
 
 sql_create_table() has database-specific foo that probably ought to be
 =head1 BUGS
 
 sql_create_table() has database-specific foo that probably ought to be
-abstracted into the DBIx::DBSchema::DBD:: modules.
+abstracted into the DBIx::DBSchema::DBD:: modules (or no?  it doesn't anymore?).
 
 
-sql_create_table may change or destroy the object's data.  If you need to use
+sql_alter_table() also has database-specific foo that ought to be abstracted
+into the DBIx::DBSchema::DBD:: modules.
+
+sql_create_table() may change or destroy the object's data.  If you need to use
 the object after sql_create_table, make a copy beforehand.
 
 Some of the logic in new_odbc might be better abstracted into Column.pm etc.
 
 the object after sql_create_table, make a copy beforehand.
 
 Some of the logic in new_odbc might be better abstracted into Column.pm etc.
 
+sql_alter_table ought to drop columns not in $new
+
+Add methods to get and set specific indices, by name? (like column COLUMN_NAME)
+
+indices method should be a setter, not just a getter?
+
 =head1 SEE ALSO
 
 L<DBIx::DBSchema>, L<DBIx::DBSchema::ColGroup::Unique>,
 =head1 SEE ALSO
 
 L<DBIx::DBSchema>, L<DBIx::DBSchema::ColGroup::Unique>,