patch from Charles Shapiro <cshapiro@numethods.com> to fix column ordering
[DBIx-DBSchema.git] / DBSchema.pm
1 package DBIx::DBSchema;
2
3 use strict;
4 use vars qw(@ISA $VERSION);
5 #use Exporter;
6 use Carp qw(confess);
7 use DBI;
8 use FreezeThaw qw(freeze thaw cmpStr);
9 use DBIx::DBSchema::Table;
10 use DBIx::DBSchema::Column;
11 use DBIx::DBSchema::ColGroup::Unique;
12 use DBIx::DBSchema::ColGroup::Index;
13
14 #@ISA = qw(Exporter);
15 @ISA = ();
16
17 $VERSION = "0.18";
18
19 =head1 NAME
20
21 DBIx::DBSchema - Database-independent schema objects
22
23 =head1 SYNOPSIS
24
25   use DBIx::DBSchema;
26
27   $schema = new DBIx::DBSchema @dbix_dbschema_table_objects;
28   $schema = new_odbc DBIx::DBSchema $dbh;
29   $schema = new_odbc DBIx::DBSchema $dsn, $user, $pass;
30   $schema = new_native DBIx::DBSchema $dbh;
31   $schema = new_native DBIx::DBSchema $dsn, $user, $pass;
32
33   $schema->save("filename");
34   $schema = load DBIx::DBSchema "filename";
35
36   $schema->addtable($dbix_dbschema_table_object);
37
38   @table_names = $schema->tables;
39
40   $DBIx_DBSchema_table_object = $schema->table("table_name");
41
42   @sql = $schema->sql($dbh);
43   @sql = $schema->sql($dsn, $username, $password);
44   @sql = $schema->sql($dsn); #doesn't connect to database - less reliable
45
46   $perl_code = $schema->pretty_print;
47   %hash = eval $perl_code;
48   use DBI qw(:sql_types); $schema = pretty_read DBIx::DBSchema \%hash;
49
50 =head1 DESCRIPTION
51
52 DBIx::DBSchema objects are collections of DBIx::DBSchema::Table objects and
53 represent a database schema.
54
55 This module implements an OO-interface to database schemas.  Using this module,
56 you can create a database schema with an OO Perl interface.  You can read the
57 schema from an existing database.  You can save the schema to disk and restore
58 it a different process.  Most importantly, DBIx::DBSchema can write SQL
59 CREATE statements statements for different databases from a single source.
60
61 Currently supported databases are MySQL and PostgreSQL.  DBIx::DBSchema will
62 attempt to use generic SQL syntax for other databases.  Assistance adding
63 support for other databases is welcomed.
64
65 =head1 METHODS
66
67 =over 4
68
69 =item new TABLE_OBJECT, TABLE_OBJECT, ...
70
71 Creates a new DBIx::DBSchema object.
72
73 =cut
74
75 sub new {
76   my($proto, @tables) = @_;
77   my %tables = map  { $_->name, $_ } @tables; #check for duplicates?
78
79   my $class = ref($proto) || $proto;
80   my $self = {
81     'tables' => \%tables,
82   };
83
84   bless ($self, $class);
85
86 }
87
88 =item new_odbc DATABASE_HANDLE | DATA_SOURCE USERNAME PASSWORD [ ATTR ]
89
90 Creates a new DBIx::DBSchema object from an existing data source, which can be
91 specified by passing an open DBI database handle, or by passing the DBI data
92 source name, username, and password.  This uses the experimental DBI type_info
93 method to create a schema with standard (ODBC) SQL column types that most
94 closely correspond to any non-portable column types.  Use this to import a
95 schema that you wish to use with many different database engines.  Although
96 primary key and (unique) index information will only be read from databases
97 with DBIx::DBSchema::DBD drivers (currently MySQL and PostgreSQL), import of
98 column names and attributes *should* work for any database.
99
100 =cut
101
102 sub new_odbc {
103   my($proto, $dbh) = (shift, shift);
104   $dbh = DBI->connect( $dbh, @_ ) or die $DBI::errstr unless ref($dbh);
105   $proto->new(
106     map { new_odbc DBIx::DBSchema::Table $dbh, $_ } _tables_from_dbh($dbh)
107   );
108 }
109
110 =item new_native DATABASE_HANDLE | DATA_SOURCE USERNAME PASSWORD [ ATTR ]
111
112 Creates a new DBIx::DBSchema object from an existing data source, which can be
113 specified by passing an open DBI database handle, or by passing the DBI data
114 source name, username and password.  This uses database-native methods to read
115 the schema, and will preserve any non-portable column types.  The method is
116 only available if there is a DBIx::DBSchema::DBD for the corresponding database engine (currently, MySQL and PostgreSQL).
117
118 =cut
119
120 sub new_native {
121   my($proto, $dbh) = (shift, shift);
122   $dbh = DBI->connect( $dbh, @_ ) or die $DBI::errstr unless ref($dbh);
123   $proto->new(
124     map { new_native DBIx::DBSchema::Table ( $dbh, $_ ) } _tables_from_dbh($dbh)
125   );
126 }
127
128 =item load FILENAME
129
130 Loads a DBIx::DBSchema object from a file.
131
132 =cut
133
134 sub load {
135   my($proto,$file)=@_; #use $proto ?
136   open(FILE,"<$file") or die "Can't open $file: $!";
137   my($string)=join('',<FILE>); #can $string have newlines?  pry not?
138   close FILE or die "Can't close $file: $!";
139   my($self)=thaw $string;
140   #no bless needed?
141   $self;
142 }
143
144 =item save FILENAME
145
146 Saves a DBIx::DBSchema object to a file.
147
148 =cut
149
150 sub save {
151   my($self,$file)=@_;
152   my($string)=freeze $self;
153   open(FILE,">$file") or die "Can't open $file: $!";
154   print FILE $string;
155   close FILE or die "Can't close file: $!";
156   my($check_self)=thaw $string;
157   die "Verify error: Can't freeze and thaw dbdef $self"
158     if (cmpStr($self,$check_self));
159 }
160
161 =item addtable TABLE_OBJECT
162
163 Adds the given DBIx::DBSchema::Table object to this DBIx::DBSchema.
164
165 =cut
166
167 sub addtable {
168   my($self,$table)=@_;
169   $self->{'tables'}->{$table->name} = $table; #check for dupliates?
170 }
171
172 =item tables 
173
174 Returns a list of the names of all tables.
175
176 =cut
177
178 sub tables {
179   my($self)=@_;
180   keys %{$self->{'tables'}};
181 }
182
183 =item table TABLENAME
184
185 Returns the specified DBIx::DBSchema::Table object.
186
187 =cut
188
189 sub table {
190   my($self,$table)=@_;
191   $self->{'tables'}->{$table};
192 }
193
194 =item sql [ DATABASE_HANDLE | DATA_SOURCE [ USERNAME PASSWORD [ ATTR ] ] ]
195
196 Returns a list of SQL `CREATE' statements for this schema.
197
198 The data source can be specified by passing an open DBI database handle, or by
199 passing the DBI data source name, username and password.  
200
201 Although the username and password are optional, it is best to call this method
202 with a database handle or data source including a valid username and password -
203 a DBI connection will be opened and the quoting and type mapping will be more
204 reliable.
205
206 If passed a DBI data source (or handle) such as `DBI:mysql:database' or
207 `DBI:Pg:dbname=database', will use syntax specific to that database engine.
208 Currently supported databases are MySQL and PostgreSQL.
209
210 If not passed a data source (or handle), or if there is no driver for the
211 specified database, will attempt to use generic SQL syntax.
212
213 =cut
214
215 sub sql {
216   my($self, $dbh) = (shift, shift);
217   my $created_dbh = 0;
218   unless ( ref($dbh) || ! @_ ) {
219     $dbh = DBI->connect( $dbh, @_ ) or die $DBI::errstr;
220     $created_dbh = 1;
221   }
222   my @r = map { $self->table($_)->sql_create_table($dbh); } $self->tables;
223   $dbh->disconnect if $created_dbh;
224   @r;
225 }
226
227 =item pretty_print
228
229 Returns the data in this schema as Perl source, suitable for assigning to a
230 hash.
231
232 =cut
233
234 sub pretty_print {
235   my($self) = @_;
236   join("},\n\n",
237     map {
238       my $table = $_;
239       "'$table' => {\n".
240         "  'columns' => [\n".
241           join("", map { 
242                          #cant because -w complains about , in qw()
243                          # (also biiiig problems with empty lengths)
244                          #"    qw( $_ ".
245                          #$self->table($table)->column($_)->type. " ".
246                          #( $self->table($table)->column($_)->null ? 'NULL' : 0 ). " ".
247                          #$self->table($table)->column($_)->length. " ),\n"
248                          "    '$_', ".
249                          "'". $self->table($table)->column($_)->type. "', ".
250                          "'". $self->table($table)->column($_)->null. "', ". 
251                          "'". $self->table($table)->column($_)->length. "', ".
252                          "'". $self->table($table)->column($_)->default. "', ".
253                          "'". $self->table($table)->column($_)->local. "',\n"
254                        } $self->table($table)->columns
255           ).
256         "  ],\n".
257         "  'primary_key' => '". $self->table($table)->primary_key. "',\n".
258         "  'unique' => [ ". join(', ',
259           map { "[ '". join("', '", @{$_}). "' ]" }
260             @{$self->table($table)->unique->lol_ref}
261           ).  " ],\n".
262         "  'index' => [ ". join(', ',
263           map { "[ '". join("', '", @{$_}). "' ]" }
264             @{$self->table($table)->index->lol_ref}
265           ). " ],\n"
266         #"  'index' => [ ".    " ],\n"
267     } $self->tables
268   ), "}\n";
269 }
270
271 =cut
272
273 =item pretty_read HASHREF
274
275 Creates a schema as specified by a data structure such as that created by
276 B<pretty_print> method.
277
278 =cut
279
280 sub pretty_read {
281   my($proto, $href) = @_;
282   my $schema = $proto->new( map {  
283     my(@columns);
284     while ( @{$href->{$_}{'columns'}} ) {
285       push @columns, DBIx::DBSchema::Column->new(
286         splice @{$href->{$_}{'columns'}}, 0, 6
287       );
288     }
289     DBIx::DBSchema::Table->new(
290       $_,
291       $href->{$_}{'primary_key'},
292       DBIx::DBSchema::ColGroup::Unique->new($href->{$_}{'unique'}),
293       DBIx::DBSchema::ColGroup::Index->new($href->{$_}{'index'}),
294       @columns,
295     );
296   } (keys %{$href}) );
297 }
298
299 # private subroutines
300
301 sub _load_driver {
302   my($dbh) = @_;
303   my $driver;
304   if ( ref($dbh) ) {
305     $driver = $dbh->{Driver}->{Name};
306   } else {
307     $dbh =~ s/^dbi:(\w*?)(?:\((.*?)\))?://i #nicked from DBI->connect
308                         or '' =~ /()/; # ensure $1 etc are empty if match fails
309     $driver = $1 or confess "can't parse data source: $dbh";
310   }
311
312   #require "DBIx/DBSchema/DBD/$driver.pm";
313   #$driver;
314   eval 'require "DBIx/DBSchema/DBD/$driver.pm"' and $driver;
315 }
316
317 sub _tables_from_dbh {
318   my($dbh) = @_;
319   my $sth = $dbh->table_info or die $dbh->errstr;
320   #map { $_->{TABLE_NAME} } grep { $_->{TABLE_TYPE} eq 'TABLE' }
321   #  @{ $sth->fetchall_arrayref({ TABLE_NAME=>1, TABLE_TYPE=>1}) };
322   map { $_->[0] } grep { $_->[1] =~ /^TABLE$/i }
323     @{ $sth->fetchall_arrayref([2,3]) };
324 }
325
326 =back
327
328 =head1 AUTHOR
329
330 Ivan Kohler <ivan-dbix-dbschema@420.am>
331
332 =head1 COPYRIGHT
333
334 Copyright (c) 2000 Ivan Kohler
335 Copyright (c) 2000 Mail Abuse Prevention System LLC
336 All rights reserved.
337 This program is free software; you can redistribute it and/or modify it under
338 the same terms as Perl itself.
339
340 =head1 BUGS
341
342 Each DBIx::DBSchema object should have a name which corresponds to its name
343 within the SQL database engine (DBI data source).
344
345 pretty_print is actually pretty ugly.
346
347 Perhaps pretty_read should eval column types so that we can use DBI
348 qw(:sql_types) here instead of externally.
349
350 =head1 SEE ALSO
351
352 L<DBIx::DBSchema::Table>, L<DBIx::DBSchema::ColGroup>,
353 L<DBIx::DBSchema::ColGroup::Unique>, L<DBIx::DBSchema::ColGroup::Index>,
354 L<DBIx::DBSchema::Column>, L<DBIx::DBSchema::DBD>, L<DBIx::DBSchema::mysql>,
355 L<DBIx::DBSchema::Pg>, L<FS::Record>, L<DBI>
356
357 =cut
358
359 1;
360