Patch from Slavin Rezic <srezic@cpan.org> to prevent quoting around numeric defaults...
[DBIx-DBSchema.git] / DBSchema / Column.pm
index 0971f01..b13d5c0 100644 (file)
@@ -1,15 +1,11 @@
 package DBIx::DBSchema::Column;
 
 use strict;
-use vars qw(@ISA $VERSION);
-#use Carp;
-#use Exporter;
-use DBIx::DBSchema::_util qw(_load_driver);
+use vars qw($VERSION);
+use Carp;
+use DBIx::DBSchema::_util qw(_load_driver _dbh);
 
-#@ISA = qw(Exporter);
-@ISA = qw();
-
-$VERSION = '0.05';
+$VERSION = '0.12';
 
 =head1 NAME
 
@@ -89,6 +85,10 @@ sub new {
   if ( ref($_[0]) ) {
     $self = shift;
   } else {
+    #carp "Old-style $class creation without named parameters is deprecated!";
+    #croak "FATAL: old-style $class creation no longer supported;".
+    #      " use named parameters";
+
     $self = { map { $_ => shift } qw(name type null length default local) };
   }
 
@@ -245,26 +245,34 @@ for other engines (if applicable) may also be supported in the future.
 =cut
 
 sub line {
-  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;
-  }
   my $driver = $dbh ? _load_driver($dbh) : '';
+  my $driver_class = "DBIx::DBSchema::DBD::${driver}";
+
+  ##
+  # type mapping
+  ## 
 
   my %typemap;
-  %typemap = eval "\%DBIx::DBSchema::DBD::${driver}::typemap" if $driver;
+  %typemap = eval "\%${driver_class}::typemap" if $driver;
   my $type = defined( $typemap{uc($self->type)} )
     ? $typemap{uc($self->type)}
     : $self->type;
 
-  my $null = $self->null;
+  ##
+  # set default for the callback...
+  ##
 
   my $default;
-  if ( defined($self->default) && !ref($self->default) && $self->default ne ''
+  my $orig_default = $self->default;
+  if ( $driver_class->can("_column_value_needs_quoting") ) {
+    if ($driver_class->_column_value_needs_quoting($self)) {
+      $default = $dbh->quote($self->default);
+    } else {
+      $default = ref($self->default) ? ${$self->default} : $self->default;
+    }
+  } elsif ( defined($self->default) && !ref($self->default) && $self->default ne ''
        && ref($dbh)
        # false laziness: nicked from FS::Record::_quote
        && ( $self->default !~ /^\-?\d+(\.\d+)?$/
@@ -273,21 +281,42 @@ sub line {
   ) {
     $default = $dbh->quote($self->default);
   } else {
-    warn "*** ref pointing to data: ". ${$self->default}
-      if $ref($self->default);
     $default = ref($self->default) ? ${$self->default} : $self->default;
   }
+  $self->default($default);
 
-  #this should be a callback into the driver
-  if ( $driver eq 'mysql' ) { #yucky mysql hack
-    $null ||= "NOT NULL";
-    $self->local('AUTO_INCREMENT') if uc($self->type) eq 'SERIAL';
-  } elsif ( $driver =~ /^(?:Pg|SQLite)$/ ) { #yucky Pg/SQLite hack
-    $null ||= "NOT NULL";
-    $null =~ s/^NULL$//;
-  }
+  ##
+  # callback into the database-specific driver
+  ##
+
+  my $dbd = "DBIx::DBSchema::DBD::$driver";
+  my $hashref = $dbd->column_callback( $dbh, $self->table_name, $self );
+
+  $self->default($orig_default);
+
+  $type = $hashref->{'effective_type'}
+    if $hashref->{'effective_type'};
+
+  my $null = $self->null;
+
+  #we seem to do this for mysql/Pg/SQLite, i think this should be the default
+  #add something to $hashref if drivers need to overrdide?
+  $null ||= "NOT NULL";
+
+  $null =~ s/^NULL$// unless $hashref->{'explicit_null'};
+
+  $default = $hashref->{'effective_default'}
+    if $hashref->{'effective_default'};
 
-  my $r = join(' ',
+  my $local = $self->local;
+  $local = $hashref->{'effective_local'}
+    if $hashref->{'effective_local'};
+
+  ##
+  # return column line
+  ## 
+
+  join(' ',
     $self->name,
     $type. ( ( defined($self->length) && $self->length )
              ? '('.$self->length.')'
@@ -298,19 +327,15 @@ sub line {
       ? 'DEFAULT '. $default
       : ''
     ),
-    ( ( $driver eq 'mysql' && defined($self->local) )
-      ? $self->local
-      : ''
-    ),
+    ( defined($local) ? $local : ''),
   );
-  $dbh->disconnect if $created_dbh;
-  $r;
 
 }
 
-=item sql_add_column
+=item sql_add_column [ DBH ] 
 
-Returns a list of SQL statements to add this column.
+Returns a list of SQL statements to add this column to an existing table.  (To
+create a new table, see L<DBIx::DBSchema::Table/sql_create_table> instead.)
 
 The data source can be specified by passing an open DBI database handle, or by
 passing the DBI data source name, username and password.  
@@ -327,98 +352,133 @@ applicable) may also be supported in the future.
 =cut
 
 sub sql_add_column {
-  my($self, $dbh) = (shift, shift);
+  my($self, $dbh) = ( shift, _dbh(@_) );
 
   die "$self: this column is not assigned to a table"
     unless $self->table_name;
 
-  #false laziness w/Table::sql_create_driver
-  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;
-  }
-
   my $driver = $dbh ? _load_driver($dbh) : '';
 
-  #eofalse
+  my @sql = ();
+  my $table = $self->table_name;
 
-  my @after_add = ();
+  my $dbd = "DBIx::DBSchema::DBD::$driver";
+  my $hashref = $dbd->add_column_callback( $dbh, $table, $self );
 
   my $real_type = '';
-  if (  $driver eq 'Pg' && $self->type eq 'serial' ) {
-    $real_type = 'serial';
-    $self->type('int');
+  if ( $hashref->{'effective_type'} ) {
+    $real_type = $self->type;
+    $self->type($hashref->{'effective_type'});
+  }
 
-    push @after_add, sub {
-      my($table, $column) = @_;
+  my $real_null = undef;
+  if ( exists($hashref->{'effective_null'}) ) {
+    $real_null = $self->null;
+    $self->null($hashref->{'effective_null'});
+  }
 
-      #needs more work for old Pg
+  push @sql, "ALTER TABLE $table ADD COLUMN ". $self->line($dbh);
 
-      my $nextval;
-      if ( $dbh->{'pg_server_version'} > 70300 ) {
-        $nextval = "nextval('public.${table}_${column}_seq'::text)";
-      } else {
-        $nextval = "nextval('${table}_${column}_seq'::text)";
-      }
+  push @sql, @{ $hashref->{'sql_after'} } if $hashref->{'sql_after'};
 
-      (
-        "ALTER TABLE $table ALTER COLUMN $column SET DEFAULT $nextval",
-        "CREATE SEQUENCE ${table}_${column}_seq",
-        "UPDATE $table SET $column = $nextval WHERE $column IS NULL",
-        #"ALTER TABLE $table ALTER $column SET NOT NULL",
-      );
+  push @sql, "ALTER TABLE $table ADD PRIMARY KEY ( ".
+             $self->table_obj->primary_key. " )"
+    if $self->name eq $self->table_obj->primary_key;
 
-    };
+  $self->type($real_type) if $real_type;
+  $self->null($real_null) if defined $real_null;
 
-  }
+  @sql;
 
-  my $real_null = undef;
-  if ( $driver eq 'Pg' && ! $self->null ) {
-    $real_null = $self->null;
-    $self->null('NULL');
+}
 
-    if ( $dbh->{'pg_server_version'} > 70300 ) {
+=item sql_alter_column PROTOTYPE_COLUMN  [ DATABASE_HANDLE | DATA_SOURCE [ USERNAME PASSWORD [ ATTR ] ] ]
 
-      push @after_add, sub {
-        my($table, $column) = @_;
-        "ALTER TABLE $table ALTER $column SET NOT NULL";
-      };
+Returns a list of SQL statements to alter this column so that it is identical
+to the provided prototype column, also a DBIx::DBSchema::Column object.
 
-    } else {
+ #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.  
+ #
+ #If passed a DBI data source (or handle) such as `DBI:Pg:dbname=database', will
+ #use PostgreSQL-specific syntax.  Non-standard syntax for other engines (if
+ #applicable) may also be supported in the future.
+ #
+ #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.
 
-      push @after_add, sub {
-        my($table, $column) = @_;
-        "UPDATE pg_attribute SET attnotnull = TRUE ".
-        " WHERE attname = '$column' ".
-        " AND attrelid = ( SELECT oid FROM pg_class WHERE relname = '$table' )";
-      };
 
-    }
+Or should, someday.  Right now it knows how to change NOT NULL into NULL and
+vice-versa.
 
-  }
+=cut
+
+sub sql_alter_column {
+  my( $self, $new, $dbh ) = ( shift, shift, _dbh(@_) );
 
-  my @r = ();
   my $table = $self->table_name;
-  my $column = $self->name;
+  die "$self: this column is not assigned to a table"
+    unless $table;
 
-  push @r, "ALTER TABLE $table ADD COLUMN ". $self->line($dbh);
+  my $name = $self->name;
 
-  push @r, &{$_}($table, $column) foreach @after_add;
+  my $driver = $dbh ? _load_driver($dbh) : '';
 
-  push @r, "ALTER TABLE $table ADD PRIMARY KEY ( ".
-             $self->table_obj->primary_key. " )"
-    if $self->name eq $self->table_obj->primary_key;
+  my @sql = ();
 
-  $self->type($real_type) if $real_type;
-  $self->null($real_null) if defined $real_null;
+  my $dbd = "DBIx::DBSchema::DBD::$driver";
+  my $hashref = $dbd->alter_column_callback( $dbh, $table, $self, $new );
+
+  # change the name...
+
+  # change the type...
 
-  $dbh->disconnect if $created_dbh;
+  if ( $hashref->{'sql_alter_null' } ) {
 
-  @r;
+    push @sql, $hashref->{'sql_alter_null'};
+
+  } else {
+
+    # change nullability from NOT NULL to NULL
+    if ( ! $self->null && $new->null ) {
+  
+      push @sql, "ALTER TABLE $table ALTER COLUMN $name DROP NOT NULL";
+  
+    }
+  
+    # change nullability from NULL to NOT NULL...
+    # this one could be more complicated, need to set a DEFAULT value and update
+    # the table first...
+    if ( $self->null && ! $new->null ) {
+  
+      push @sql, "ALTER TABLE $table ALTER COLUMN $name SET NOT NULL";
+  
+    }
+
+  }
+  
+  # change other stuff...
+
+  @sql;
 
 }
+=item sql_drop_column [ DBH ] 
+
+Returns a list of SQL statements to drop this column from an existing table.
+
+The optional database handle or DBI data source/username/password is not yet
+used.
+
+=cut
+
+sub sql_drop_column {
+ my( $self, $dbh ) = ( shift, _dbh(@_) );
+ my $table = $self->table_name;
+ my $name = $self->name;
+ ("ALTER TABLE $table DROP COLUMN $name"); # XXX what about indexes???
+}
 
 =back
 
@@ -428,13 +488,17 @@ Ivan Kohler <ivan-dbix-dbschema@420.am>
 
 =head1 COPYRIGHT
 
-Copyright (c) 2000-2005 Ivan Kohler
+Copyright (c) 2000-2006 Ivan Kohler
+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.
 
 =head1 BUGS
 
+The new() method should warn that 
+"Old-style $class creation without named parameters is deprecated!"
+
 Better documentation is needed for sql_add_column
 
 line() and sql_add_column() hav database-specific foo that should be abstracted