File Coverage

blib/lib/Autodia/Handler/DBI.pm
Criterion Covered Total %
statement 22 24 91.6
branch n/a
condition n/a
subroutine 8 8 100.0
pod n/a
total 30 32 93.7


line stmt bran cond sub pod time code
1             ################################################################
2             # AutoDIA - Automatic Dia XML. (C)Copyright 2001 A Trevena #
3             # #
4             # AutoDIA comes with ABSOLUTELY NO WARRANTY; see COPYING file #
5             # This is free software, and you are welcome to redistribute #
6             # it under certain conditions; see COPYING file for details #
7             ################################################################
8             package Autodia::Handler::DBI;
9              
10             require Exporter;
11              
12 1     1   1442 use strict;
  1         1  
  1         35  
13              
14 1     1   6 use warnings;
  1         2  
  1         31  
15 1     1   5 use warnings::register;
  1         2  
  1         134  
16              
17 1     1   5 use vars qw($VERSION @ISA @EXPORT);
  1         3  
  1         58  
18 1     1   5 use Autodia::Handler;
  1         2  
  1         48  
19              
20             @ISA = qw(Autodia::Handler Exporter);
21              
22 1     1   5 use Autodia::Diagram;
  1         2  
  1         22  
23 1     1   4 use Data::Dumper;
  1         2  
  1         42  
24 1     1   451 use DBI;
  0            
  0            
25              
26             #---------------------------------------------------------------
27              
28             #####################
29             # Constructor Methods
30              
31             # new inherited from Autodia::Handler
32              
33             #------------------------------------------------------------------------
34             # Access Methods
35              
36             # parse_file inherited from Autodia::Handler
37              
38             #-----------------------------------------------------------------------------
39             # Internal Methods
40              
41             # _initialise inherited from Autodia::Handler
42              
43             sub _parse_file { # parses dbi-connection string
44             my $self = shift();
45             my $filename = shift();
46             my %config = %{$self->{Config}};
47             $self->{Diagram}->directed(0);
48              
49             # new dbi connection
50             my $dbh = DBI->connect("DBI:$filename", $config{username}, $config{password});
51              
52             my $escape_tablenames = 0;
53             my $unescape_tablenames=0;
54             my $database_type = $dbh->get_info( 17 );
55             warn "database_type : $database_type\n";
56             my ($scheme, $driver, $attr_string, $attr_hash, $driver_dsn) = DBI->parse_dsn("DBI:$filename") or die "Can't parse DBI DSN '$filename'";
57             my $dbname;
58             if ($driver_dsn =~ m/(?:db|dbname)=([^\:]+)/) {
59             $dbname = $1;
60             } else {
61             ( $dbname = $driver_dsn) =~ s/([^\:]+)/$1/;
62             }
63              
64             my $schema = '' ;
65             # only keep tables in schema public for PostgreSQL
66             # could be given as a parameter... (+ a list of tables...)
67             $schema = 'public' if (lc($database_type) =~ m/(oracle|postgres)/);
68              
69             # Manage database tablenames that need to be escaped before calling DBI
70             # and those that need to be unescaped before calling DBI
71             $escape_tablenames = 1 if (lc($database_type) =~ m/(oracle|postgres)/);
72             $unescape_tablenames = 1 if (lc($database_type) =~ m/(mysql)/);
73              
74             # pre-process tables
75              
76             foreach my $table ($dbh->tables(undef, $schema, '%', '')) {
77             $table =~ s/['`"]//g;
78             $table =~ s/.*\.(.*)$/$1/;
79             my $esc_table = $table;
80             $esc_table = qq{"$esc_table"} if ($escape_tablenames);
81             my $sth = $dbh->prepare("select * from $esc_table where 1 = 0");
82             $sth->execute;
83             $self->{tables}{$table}{fields} = $sth->{NAME};
84             $sth->finish;
85             }
86              
87              
88             # got to about here applying dbi datatypes patch
89             foreach my $table (keys %{$self->{tables}}) {
90             # create new 'class' representing table
91             my $Class = Autodia::Diagram::Class->new($table);
92             # add 'class' to diagram
93             $self->{Diagram}->add_class($Class);
94              
95             # get fields
96             my $esc_table = $table;
97             $esc_table = qq{"${dbname}.$esc_table"} if ($escape_tablenames);
98              
99             warn "using dbname $dbname / table $esc_table\n";
100              
101             my @key_columns;
102             my $primary_key = { name=>'Key', type=>'Primary', Params=>[], visibility=>0, };
103             my $sth = $dbh->primary_key_info( $schema || undef, $dbname, $esc_table );
104             if (defined $sth) {
105             @key_columns = keys %{$sth->fetchall_hashref('COLUMN_NAME')};
106             } else {
107             warn "trying dbh -> primary key method using schema $schema, dbname : $dbname, table $esc_table\n";
108             # from DBIx::Class::Schema::Loader::DBI / Rose::DBI
109             @key_columns = map { lc } $dbh->primary_key($schema || undef, $dbname, $esc_table);
110              
111             }
112             warn "got key columns for table $esc_table : @key_columns\n";
113              
114             if (@key_columns) {
115             push (@{$primary_key->{Params}}, map ({ Name=>$_, Type=>''}, @key_columns));
116             $Class->add_operation($primary_key);
117             }
118              
119             # FIXME : need to subclass db's that don't work
120             # try using DBD, then use subclass to do horrid hacks
121              
122             my $guess_foreign_keys = 1;
123              
124             # get foreign keys
125             $sth = $dbh->foreign_key_info( $schema || undef, $dbname, '', $schema || undef, $dbname, $esc_table );
126             if ($sth) {
127             my %rels;
128              
129             my $i = 1; # for unnamed rels, which hopefully have only 1 column ...
130             while(my $raw_rel = $sth->fetchrow_arrayref) {
131             $guess_foreign_keys = 0 if ($guess_foreign_keys);
132             warn "got relation $raw_rel\n";
133             my $pk_tbl = $raw_rel->[2];
134             my $pk_col = lc $raw_rel->[3];
135             my $fk_col = lc $raw_rel->[7];
136             my $relid = ($raw_rel->[11] || ( "__dcsld__" . $i++ ));
137             $rels{$relid}->{tbl} = $pk_tbl;
138             $rels{$relid}->{cols}->{$pk_col} = $fk_col;
139              
140             push(@{$self->{foreign_tables}{$pk_tbl}}, {field => $pk_col, table => $esc_table, class => $Class });
141             $Class->add_operation( { name=>'Key', type=>'Foreign', Params=>[ { Name => $pk_col }], visibility=>0, } );
142             }
143             $sth->finish;
144             }
145              
146              
147             for my $field (@{$self->{tables}{$table}{fields}}) {
148             my $sth = $dbh->column_info( $schema || undef, $dbname, $esc_table, $field );
149             my $field_info = $sth->fetchrow_hashref;
150             $Class->add_attribute({
151             name => $field,
152             visibility => 0,
153             type => $field_info->{TYPE_NAME},
154             });
155              
156             if ($guess_foreign_keys) {
157             if (my $dep = $self->_guess_foreign_key($table, $field)) {
158             # fix - need to handle multiple relations per table
159             push(@{$self->{foreign_tables}{$dep}}, {field => $field, table => $esc_table, class => $Class });
160             $Class->add_operation( { name=>'Key', type=>'Foreign', Params=>[ { Name => $field, Type => $field_info->{TYPE_NAME}, }], visibility=>0, } );
161             }
162             }
163             }
164             }
165              
166             # fix - need to handle multiple relations per table
167             foreach my $fk_table (keys %{$self->{foreign_tables}} ) {
168             foreach my $relation ( @{$self->{foreign_tables}{$fk_table}}) {
169             $self->_add_foreign_keytable($relation->{table},
170             $relation->{field},
171             $relation->{class},
172             $fk_table);
173             }
174             }
175              
176             $dbh->disconnect;
177             }
178              
179              
180             sub _add_foreign_keytable {
181             my ($self,$table,$field,$Class,$dep) = @_;
182              
183             my $Superclass = Autodia::Diagram::Superclass->new($dep);
184             my $exists_already = $self->{Diagram}->add_superclass($Superclass);
185             $Superclass = $exists_already if (ref $exists_already);
186              
187             # create new relationship
188             my $Relationship = Autodia::Diagram::Relation->new($Class, $Superclass);
189             # add Relationship to superclass
190             $Superclass->add_relation($Relationship);
191             # add Relationship to class
192             $Class->add_relation($Relationship);
193             # add Relationship to diagram
194             $self->{Diagram}->add_relation($Relationship);
195              
196             return;
197             }
198              
199             sub _guess_foreign_key {
200             my ($self, $table, $field) = @_;
201             my $is_fk = undef;
202             $field =~ s/'"`//g;
203              
204             if ($field =~ m/^(.*)_u?id$/i) {
205             my $foreign_table = $1;
206             unless ($foreign_table eq $table) {
207             $is_fk = $foreign_table if ($self->{tables}{$foreign_table});
208             }
209             } elsif (($field ne $table ) && ($self->{tables}{$field})) {
210             $is_fk = $field;
211             }
212             return $is_fk;
213             }
214              
215             sub _discard_line
216             {
217             warn "not implemented\n";
218             return 0;
219             }
220              
221             1;
222              
223             ###############################################################################
224              
225             =head1 NAME
226              
227             Autodia::Handler::DBI.pm - AutoDia handler for DBI connections
228              
229             =head1 INTRODUCTION
230              
231             This module parses the contents of a database through a dbi connection and builds a diagram
232              
233             %language_handlers = { .. , dbi => "Autodia::Handler::DBI", .. };
234              
235             =head1 CONSTRUCTION METHOD
236              
237             use Autodia::Handler::DBI;
238              
239             my $handler = Autodia::Handler::DBI->New(\%Config);
240             This creates a new handler using the Configuration hash to provide rules selected at the command line.
241              
242             =head1 ACCESS METHODS
243              
244             $handler->Parse($connection); # where connection includes full or dbi connection string
245              
246             $handler->output(); # any arguments are ignored.
247              
248             =cut
249              
250              
251              
252              
253              
254