File Coverage

blib/lib/DBIx/Schema/DSL/Dumper.pm
Criterion Covered Total %
statement 24 162 14.8
branch 0 84 0.0
condition 0 45 0.0
subroutine 8 16 50.0
pod 0 1 0.0
total 32 308 10.3


line stmt bran cond sub pod time code
1             package DBIx::Schema::DSL::Dumper;
2 1     1   486 use 5.008001;
  1         2  
  1         30  
3 1     1   4 use strict;
  1         1  
  1         23  
4 1     1   3 use warnings;
  1         8  
  1         24  
5 1     1   392 use DBIx::Inspector;
  1         10148  
  1         45  
6 1     1   12 use DBIx::Inspector::Iterator;
  1         2  
  1         24  
7 1     1   6 use Carp ();
  1         3  
  1         47  
8              
9             our $VERSION = "0.05";
10              
11             # XXX copy from SQL::Translator::Parser::DBI-1.59
12 1         2163 use constant DRIVERS => {
13             mysql => 'MySQL',
14             odbc => 'SQLServer',
15             oracle => 'Oracle',
16             pg => 'PostgreSQL',
17             sqlite => 'SQLite',
18             sybase => 'Sybase',
19             pg => 'PostgreSQL',
20             db2 => 'DB2',
21 1     1   6 };
  1         1  
22              
23             sub dump {
24 0     0 0   my $class = shift;
25 0 0         my %args = @_==1 ? %{$_[0]} : @_;
  0            
26              
27 0 0         my $dbh = $args{dbh} or Carp::croak("missing mandatory parameter 'dbh'");
28              
29 0           my $inspector = DBIx::Inspector->new(dbh => $dbh);
30              
31 0           my $ret = "";
32              
33 0 0         if ( ref $args{tables} eq "ARRAY" ) {
    0          
34 0           for my $table_name (@{ $args{tables} }) {
  0            
35 0           $ret .= _render_table($inspector->table($table_name), \%args);
36             }
37             }
38             elsif ( $args{tables} ) {
39 0           $ret .= _render_table($inspector->table($args{tables}), \%args);
40             }
41             else {
42 0 0         my $pkg = $args{pkg} or Carp::croak("missing mandatory parameter 'pkg'");
43              
44 0           $ret .= "package ${pkg};\n";
45 0           $ret .= "use strict;\n";
46 0           $ret .= "use warnings;\n";
47 0           $ret .= "use DBIx::Schema::DSL;\n";
48 0           $ret .= "\n";
49              
50 0 0         my $db_type = $dbh->{'Driver'}{'Name'} or die 'Cannot determine DBI type';
51 0 0         my $driver = DRIVERS->{ lc $db_type } or warn "$db_type not supported";
52 0 0         $ret .= sprintf("database '%s';\n", $driver) if $driver;
53 0 0         $ret .= "default_unsigned;\n" if $args{default_unsigned};
54 0 0         $ret .= "default_not_null;\n" if $args{default_not_null};
55 0           $ret .= "\n";
56              
57 0 0         if ($args{table_options}) {
58 0           $ret .= "add_table_options\n";
59 0           my @table_options;
60 0           for my $key (keys %{$args{table_options}}) {
  0            
61 0           push @table_options => sprintf(" '%s' => '%s'", $key, $args{table_options}->{$key})
62             }
63 0           $ret .= join ",\n", @table_options;
64 0           $ret .= ";\n\n";
65             }
66              
67 0           for my $table_info (sort { $a->name cmp $b->name } $inspector->tables) {
  0            
68 0           $ret .= _render_table($table_info, \%args);
69             }
70 0           $ret .= "1;\n";
71             }
72              
73 0           return $ret;
74             }
75              
76              
77             sub _render_table {
78 0     0     my ($table_info, $args) = @_;
79              
80 0           my $ret = "";
81              
82 0           $ret .= sprintf("create_table '%s' => columns {\n", $table_info->name);
83              
84 0           my @primary_key_names = map { $_->name } $table_info->primary_key;
  0            
85              
86 0           $args = +{ %$args, primary_key_names => \@primary_key_names }; # XXX
87              
88 0           for my $col ($table_info->columns) {
89 0           $ret .= _render_column($col, $table_info, $args);
90             }
91              
92 0           $ret .= _render_index($table_info, $args);
93              
94 0           $ret .= "};\n\n";
95              
96 0           return $ret;
97             }
98              
99             sub _render_column {
100 0     0     my ($column_info, $table_info, $args) = @_;
101              
102 0           my $ret = "";
103 0           $ret .= sprintf(" column '%s'", $column_info->name);
104              
105 0           my ($type, @opt) = split / /, $column_info->type_name;
106              
107 0 0         if ($column_info->{MYSQL_TYPE_NAME}) {
108 0           push @opt => split / /, $column_info->{MYSQL_TYPE_NAME};
109             }
110              
111 0           $ret .= sprintf(", '%s'", $type);
112              
113 0           my %opt = map { lc($_) => 1 } @opt;
  0            
114              
115 0 0         if (lc($type) =~ /^(enum|set)$/) {
116             # XXX
117 0           $ret .= sprintf(" => ['%s']", join "','", @{$column_info->{MYSQL_VALUES}});
  0            
118             }
119              
120 0 0         $ret .= ", signed" if $opt{signed};
121 0 0 0       $ret .= ", unsigned" if $opt{unsigned} && !$args->{default_unsigned};
122              
123 0 0         if (defined $column_info->column_size) {
124 0           my $column_size;
125              
126 0 0 0       if (lc($type) eq 'decimal') {
    0 0        
    0 0        
    0 0        
    0          
127             # XXX
128 0           $column_size = sprintf("[%d, %d]", $column_info->column_size, $column_info->{DECIMAL_DIGITS});
129             }
130             elsif (lc($type) =~ /^(enum|set)$/) {
131             ;;
132             }
133             # TODO use DBIx::Schema::DSL->context->default_varchar_size
134             elsif (lc($type) eq 'varchar' && $column_info->column_size == 255) {
135             ;;
136             }
137             elsif (
138             lc($type) =~ /^(int|integer)$/ &&
139             (
140             $opt{unsigned} && $column_info->column_size == 10
141             or
142             !$opt{unsigned} && $column_info->column_size == 11
143             )
144             ) {
145             ;;
146             }
147             elsif ($column_info->{MYSQL_TYPE_NAME} && $column_info->{MYSQL_TYPE_NAME} !~ $column_info->column_size) {
148             ;;
149             }
150             else {
151 0           $column_size = $column_info->column_size;
152             }
153              
154              
155 0 0         $ret .= sprintf(", size => %s", $column_size) if $column_size;
156             }
157              
158 0 0         $ret .= ", null" if $column_info->nullable;
159 0 0 0       $ret .= ", not_null" if !$column_info->nullable && !$args->{default_not_null};
160              
161 0 0         if (defined $column_info->column_def) {
162 0           my $column_def = $column_info->column_def;
163              
164 0 0 0       if ($type =~ /^(TIMESTAMP|DATETIME)$/ && $column_def eq 'CURRENT_TIMESTAMP') {
165 0           $ret .= sprintf(", default => \\'%s'", $column_def)
166             }
167             else {
168 0           $ret .= sprintf(", default => '%s'", $column_def)
169             }
170             }
171              
172 0 0 0       if (@{$args->{primary_key_names}} == 1 && $args->{primary_key_names}->[0] eq $column_info->name) {
  0            
173 0           $ret .= ", primary_key"
174             }
175              
176 0 0 0       if (
      0        
177             $opt{auto_increment} or
178             # XXX
179             ($args->{dbh}->{'Driver'}{'Name'} eq 'mysql' && $column_info->{MYSQL_IS_AUTO_INCREMENT})
180             ) {
181 0           $ret .= ", auto_increment"
182             }
183              
184 0           $ret .= ";\n";
185              
186 0           return $ret;
187             }
188              
189             sub _render_index {
190 0     0     my ($table_info, $args) = @_;
191              
192 0           my @fk_list = $table_info->fk_foreign_keys;
193              
194 0           my $ret = "";
195              
196             # primary key
197 0 0         if (@{$args->{primary_key_names}} > 1) {
  0            
198 0           $ret .= "\n";
199 0           $ret .= sprintf(" set_primary_key('%s');\n", join "','", @{$args->{primary_key_names}});
  0            
200             }
201              
202             # index
203             {
204 0           my $itr = _statistics_info($args->{dbh}, $table_info->schema, $table_info->name);
  0            
205 0           my %pk_name = map { $_ => 1 } @{$args->{primary_key_names}};
  0            
  0            
206 0           my %fk_name = map { $_->fkcolumn_name => 1 } @fk_list;
  0            
207              
208 0           my %index_info;
209 0           while (my $index_key = $itr->next) {
210 0 0         next if $pk_name{$index_key->column_name};
211 0 0         next if $fk_name{$index_key->column_name};
212              
213 0           push @{$index_info{$index_key->index_name}} => $index_key;
  0            
214             }
215              
216 0 0         $ret .= "\n" if %index_info;
217 0           for my $index_name (sort keys %index_info) {
218 0           my @index_keys = @{$index_info{$index_name}};
  0            
219 0           my @column_names = map { $_->column_name } @index_keys;
  0            
220              
221 0           $ret .= sprintf(" add_%sindex('%s' => [%s]%s);\n",
222             $index_keys[0]->non_unique ? '' : 'unique_',
223             $index_name,
224 0 0 0       (join ",", (map { q{'}.$_.q{'} } @column_names)),
    0          
225             $index_keys[0]->non_unique && $index_keys[0]->type ? sprintf(", '%s'", $index_keys[0]->type) : '',
226             );
227             }
228             }
229              
230             # foreign key
231             # FIXME not supported UPDATE_RULE, DELETE_RULE
232 0 0         if (@fk_list) {
233 0           $ret .= "\n";
234 0           for my $fk (@fk_list) {
235 0 0 0       if ($fk->pktable_name && $fk->fkcolumn_name eq sprintf('%s_id', $fk->pktable_name)) {
    0 0        
    0 0        
      0        
236 0           $ret .= sprintf(" belongs_to('%s')\n", $fk->pktable_name)
237             }
238             elsif ($fk->fkcolumn_name eq 'id' && $fk->pkcolumn_name eq sprintf('%s_id', $fk->fktable_name)) {
239              
240 0           my $itr = _statistics_info($args->{dbh}, $table_info->schema, $fk->pktable_name);
241 0           while (my $index_key = $itr->next) {
242 0 0         if ($index_key->column_name eq $fk->pkcolumn_name) {
243 0 0         my $has = $index_key->non_unique ? 'has_many' : 'has_one';
244 0           $ret .= sprintf(" %s('%s')\n", $has, $fk->pktable_name);
245 0           last;
246             }
247             }
248             }
249             elsif ($fk->fkcolumn_name && $fk->pktable_name && $fk->pkcolumn_name) {
250 0           $ret .= sprintf(" foreign_key('%s','%s','%s')\n", $fk->fkcolumn_name, $fk->pktable_name, $fk->pkcolumn_name);
251             }
252             }
253             }
254              
255 0           return $ret;
256             }
257              
258             # EXPERIMENTAL: https://metacpan.org/pod/DBI#statistics_info
259             sub _statistics_info {
260 0     0     my ($dbh, $schema, $table_name) = @_;
261              
262 0           my $sth;
263 0 0         if ($dbh->{'Driver'}{'Name'} eq 'mysql') {
264             # TODO p-r DBD::mysqld ??
265 0           my $sql = q{
266             SELECT
267             *
268             FROM
269             INFORMATION_SCHEMA.STATISTICS
270             WHERE
271             table_schema = ?
272             AND table_name = ?
273             };
274 0           $sth = $dbh->prepare($sql);
275 0           $sth->execute($schema, $table_name);
276             }
277             else {
278 0           $sth = $dbh->statistics_info(undef, undef, $table_name, undef, undef);
279             }
280              
281             DBIx::Inspector::Iterator->new(
282             sth => $sth,
283             callback => sub {
284             # TODO p-r DBIx::Inspector ??
285 0     0     my $row = shift;
286 0           DBIx::Inspector::Statics->new($row);
287             },
288 0           );
289             }
290              
291             package # hide from PAUSE
292             DBIx::Inspector::Statics;
293              
294             sub new {
295 0     0     my $class = shift;
296 0 0         my %args = @_ == 1 ? %{ $_[0] } : @_;
  0            
297 0           bless {%args}, $class;
298             }
299              
300             {
301 1     1   11 no strict 'refs';
  1         1  
  1         97  
302             for my $k (
303             qw/
304             TABLE_CAT
305             TABLE_SCHEM
306             TABLE_NAME
307             NON_UNIQUE
308             INDEX_QUALIFIER
309             INDEX_NAME
310             TYPE
311             ORDINAL_POSITION
312             COLUMN_NAME
313             ASC_OR_DESC
314             CARDINALITY
315             PAGES
316             FILTER_CONDITION
317             /
318             )
319             {
320 0     0     *{ __PACKAGE__ . "::" . lc($k) } = sub { $_[0]->{$k} };
321             }
322             }
323              
324              
325             1;
326             __END__