depend on DBD::Pg 1.32 or 1.41+ (1.40 was bunk)
[DBIx-DBSchema.git] / DBSchema.pm
index 327a0cc..0e7b543 100644 (file)
@@ -3,15 +3,18 @@ package DBIx::DBSchema;
 use strict;
 use vars qw(@ISA $VERSION);
 #use Exporter;
-#use Carp qw(verbose);
+use Carp qw(confess);
 use DBI;
-use FreezeThaw qw(freeze thaw cmpStr);
+use Storable;
 use DBIx::DBSchema::Table;
+use DBIx::DBSchema::Column;
+use DBIx::DBSchema::ColGroup::Unique;
+use DBIx::DBSchema::ColGroup::Index;
 
 #@ISA = qw(Exporter);
 @ISA = ();
 
-$VERSION = "0.1";
+$VERSION = "0.25";
 
 =head1 NAME
 
@@ -36,17 +39,30 @@ DBIx::DBSchema - Database-independent schema objects
 
   $DBIx_DBSchema_table_object = $schema->table("table_name");
 
-  $sql_string = $schema->sql($dsn);
+  @sql = $schema->sql($dbh);
+  @sql = $schema->sql($dsn, $username, $password);
+  @sql = $schema->sql($dsn); #doesn't connect to database - less reliable
 
   $perl_code = $schema->pretty_print;
   %hash = eval $perl_code;
-  $schema = pretty_read DBIx::DBSchema \%hash;
+  use DBI qw(:sql_types); $schema = pretty_read DBIx::DBSchema \%hash;
 
 =head1 DESCRIPTION
 
 DBIx::DBSchema objects are collections of DBIx::DBSchema::Table objects and
 represent a database schema.
 
+This module implements an OO-interface to database schemas.  Using this module,
+you can create a database schema with an OO Perl interface.  You can read the
+schema from an existing database.  You can save the schema to disk and restore
+it a different process.  Most importantly, DBIx::DBSchema can write SQL
+CREATE statements statements for different databases from a single source.
+
+Currently supported databases are MySQL and PostgreSQL.  Sybase support is
+partially implemented.  DBIx::DBSchema will attempt to use generic SQL syntax
+for other databases.  Assistance adding support for other databases is
+welcomed.  See L<DBIx::DBSchema::DBD>, "Driver Writer's Guide and Base Class".
+
 =head1 METHODS
 
 =over 4
@@ -70,7 +86,7 @@ sub new {
 
 }
 
-=item new_odbc DATABASE_HANDLE || DATA_SOURCE USERNAME PASSWORD [ ATTR ]
+=item new_odbc DATABASE_HANDLE | DATA_SOURCE USERNAME PASSWORD [ ATTR ]
 
 Creates a new DBIx::DBSchema object from an existing data source, which can be
 specified by passing an open DBI database handle, or by passing the DBI data
@@ -80,7 +96,9 @@ closely correspond to any non-portable column types.  Use this to import a
 schema that you wish to use with many different database engines.  Although
 primary key and (unique) index information will only be read from databases
 with DBIx::DBSchema::DBD drivers (currently MySQL and PostgreSQL), import of
-column names and attributes *should* work for any database.
+column names and attributes *should* work for any database.  Note that this
+method only uses "ODBC" column types; it does not require or use an ODBC
+driver.
 
 =cut
 
@@ -92,7 +110,7 @@ sub new_odbc {
   );
 }
 
-=item new_native DATABASE_HANDLE || DATA_SOURCE USERNAME PASSWORD [ ATTR ]
+=item new_native DATABASE_HANDLE | DATA_SOURCE USERNAME PASSWORD [ ATTR ]
 
 Creates a new DBIx::DBSchema object from an existing data source, which can be
 specified by passing an open DBI database handle, or by passing the DBI data
@@ -118,12 +136,23 @@ Loads a DBIx::DBSchema object from a file.
 
 sub load {
   my($proto,$file)=@_; #use $proto ?
-  open(FILE,"<$file") or die "Can't open $file: $!";
-  my($string)=join('',<FILE>); #can $string have newlines?  pry not?
-  close FILE or die "Can't close $file: $!";
-  my($self)=thaw $string;
-  #no bless needed?
+
+  my $self;
+
+  #first try Storable
+  eval { $self = Storable::retrieve($file); };
+
+  if ( $@ && $@ =~ /not.*storable/i ) { #then try FreezeThaw
+    eval "use FreezeThaw;";
+    die $@ if $@;
+    open(FILE,"<$file") or die "Can't open $file: $!";
+    my $string = join('',<FILE>);
+    close FILE or die "Can't close $file: $!";
+    ($self) = FreezeThaw::thaw($string);
+  }
+
   $self;
+
 }
 
 =item save FILENAME
@@ -133,14 +162,8 @@ Saves a DBIx::DBSchema object to a file.
 =cut
 
 sub save {
-  my($self,$file)=@_;
-  my($string)=freeze $self;
-  open(FILE,">$file") or die "Can't open $file: $!";
-  print FILE $string;
-  close FILE or die "Can't close file: $!";
-  my($check_self)=thaw $string;
-  die "Verify error: Can't freeze and thaw dbdef $self"
-    if (cmpStr($self,$check_self));
+  #my($self, $file) = @_;
+  Storable::nstore(@_);
 }
 
 =item addtable TABLE_OBJECT
@@ -176,22 +199,37 @@ sub table {
   $self->{'tables'}->{$table};
 }
 
-=item sql_string [ DATASRC ]
+=item sql [ DATABASE_HANDLE | DATA_SOURCE [ USERNAME PASSWORD [ ATTR ] ] ]
 
 Returns a list of SQL `CREATE' statements for this schema.
 
-If passed a DBI data source such as `DBI:mysql:database' or
+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' 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 if there is no driver for the specified
-database, will attempt to use generic SQL syntax.
+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_string {
-  my($self, $datasrc) = @_;
-  map { $self->table($_)->sql_create_table($datasrc); } $self->tables;
+sub sql {
+  my($self, $dbh) = (shift, shift);
+  my $created_dbh = 0;
+  unless ( ref($dbh) || ! @_ ) {
+    $dbh = DBI->connect( $dbh, @_ ) or die $DBI::errstr;
+    $created_dbh = 1;
+  }
+  my @r = map { $self->table($_)->sql_create_table($dbh); } $self->tables;
+  $dbh->disconnect if $created_dbh;
+  @r;
 }
 
 =item pretty_print
@@ -209,10 +247,18 @@ sub pretty_print {
       "'$table' => {\n".
         "  'columns' => [\n".
           join("", map { 
+                         #cant because -w complains about , in qw()
+                         # (also biiiig problems with empty lengths)
+                         #"    qw( $_ ".
+                         #$self->table($table)->column($_)->type. " ".
+                         #( $self->table($table)->column($_)->null ? 'NULL' : 0 ). " ".
+                         #$self->table($table)->column($_)->length. " ),\n"
                          "    '$_', ".
                          "'". $self->table($table)->column($_)->type. "', ".
-                         "'". $self->table($table)->column($_)->null. "', ".
-                         "'". $self->table($table)->column($_)->length. "'\n"
+                         "'". $self->table($table)->column($_)->null. "', ". 
+                         "'". $self->table($table)->column($_)->length. "', ".
+                         "'". $self->table($table)->column($_)->default. "', ".
+                         "'". $self->table($table)->column($_)->local. "',\n"
                        } $self->table($table)->columns
           ).
         "  ],\n".
@@ -227,7 +273,7 @@ sub pretty_print {
           ). " ],\n"
         #"  'index' => [ ".    " ],\n"
     } $self->tables
-  ), "}\n";
+  ). "}\n";
 }
 
 =cut
@@ -240,18 +286,40 @@ B<pretty_print> method.
 =cut
 
 sub pretty_read {
-  die "unimplemented (pull from fs-setup)";
-  my($proto) = @_;
+  my($proto, $href) = @_;
+  my $schema = $proto->new( map {  
+    my(@columns);
+    while ( @{$href->{$_}{'columns'}} ) {
+      push @columns, DBIx::DBSchema::Column->new(
+        splice @{$href->{$_}{'columns'}}, 0, 6
+      );
+    }
+    DBIx::DBSchema::Table->new(
+      $_,
+      $href->{$_}{'primary_key'},
+      DBIx::DBSchema::ColGroup::Unique->new($href->{$_}{'unique'}),
+      DBIx::DBSchema::ColGroup::Index->new($href->{$_}{'index'}),
+      @columns,
+    );
+  } (keys %{$href}) );
 }
 
 # private subroutines
 
 sub _load_driver {
   my($dbh) = @_;
-  my $driver = $dbh->{Driver}->{Name};
+  my $driver;
+  if ( ref($dbh) ) {
+    $driver = $dbh->{Driver}->{Name};
+  } else {
+    $dbh =~ s/^dbi:(\w*?)(?:\((.*?)\))?://i #nicked from DBI->connect
+                        or '' =~ /()/; # ensure $1 etc are empty if match fails
+    $driver = $1 or confess "can't parse data source: $dbh";
+  }
+
   #require "DBIx/DBSchema/DBD/$driver.pm";
   #$driver;
-  eval 'require "DBIx/DBSchema/DBD/$driver.pm"' and $driver;
+  eval 'require "DBIx/DBSchema/DBD/$driver.pm"' and $driver or die $@;
 }
 
 sub _tables_from_dbh {
@@ -269,9 +337,12 @@ sub _tables_from_dbh {
 
 Ivan Kohler <ivan-dbix-dbschema@420.am>
 
+Charles Shapiro <charles.shapiro@numethods.com> and Mitchell Friedman
+<mitchell.friedman@numethods.com> contributed the start of a Sybase driver.
+
 =head1 COPYRIGHT
 
-Copyright (c) 2000 Ivan Kohler
+Copyright (c) 2000-2005 Ivan Kohler
 Copyright (c) 2000 Mail Abuse Prevention System LLC
 All rights reserved.
 This program is free software; you can redistribute it and/or modify it under
@@ -282,14 +353,18 @@ the same terms as Perl itself.
 Each DBIx::DBSchema object should have a name which corresponds to its name
 within the SQL database engine (DBI data source).
 
-pretty_print is atrocious.
+pretty_print is actually pretty ugly.
+
+Perhaps pretty_read should eval column types so that we can use DBI
+qw(:sql_types) here instead of externally.
 
 =head1 SEE ALSO
 
 L<DBIx::DBSchema::Table>, L<DBIx::DBSchema::ColGroup>,
 L<DBIx::DBSchema::ColGroup::Unique>, L<DBIx::DBSchema::ColGroup::Index>,
-L<DBIx::DBSchema::Column>, L<DBIx::DBSchema::DBD>, L<DBIx::DBSchema::mysql>,
-L<DBIx::DBSchema::Pg>, L<FS::Record>, L<DBI>
+L<DBIx::DBSchema::Column>, L<DBIx::DBSchema::DBD>,
+L<DBIx::DBSchema::DBD::mysql>, L<DBIx::DBSchema::DBD::Pg>, L<FS::Record>,
+L<DBI>
 
 =cut