File Coverage

blib/lib/DBIx/Schema/DSL/Dumper.pm
Criterion Covered Total %
statement 24 154 15.5
branch 0 84 0.0
condition 0 45 0.0
subroutine 8 16 50.0
pod 0 1 0.0
total 32 300 10.6


line stmt bran cond sub pod time code
1             package DBIx::Schema::DSL::Dumper;
2 1     1   521 use 5.008001;
  1         3  
  1         33  
3 1     1   4 use strict;
  1         1  
  1         24  
4 1     1   4 use warnings;
  1         6  
  1         21  
5 1     1   447 use DBIx::Inspector;
  1         9019  
  1         33  
6 1     1   6 use DBIx::Inspector::Iterator;
  1         2  
  1         19  
7 1     1   5 use Carp ();
  1         2  
  1         53  
8              
9             our $VERSION = "0.07";
10              
11             # XXX copy from SQL::Translator::Parser::DBI-1.59
12 1         2840 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   5 };
  1         2  
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           for my $col ($table_info->columns) {
85 0           $ret .= _render_column($col, $table_info, $args);
86             }
87              
88 0           $ret .= _render_index($table_info, $args);
89              
90 0           $ret .= "};\n\n";
91              
92 0           return $ret;
93             }
94              
95             sub _render_column {
96 0     0     my ($column_info, $table_info, $args) = @_;
97              
98 0           my $ret = "";
99 0           $ret .= sprintf(" column '%s'", $column_info->name);
100              
101 0           my ($type, @opt) = split / /, $column_info->type_name;
102              
103 0 0         if ($column_info->{MYSQL_TYPE_NAME}) {
104 0           push @opt => split / /, $column_info->{MYSQL_TYPE_NAME};
105             }
106              
107 0           $ret .= sprintf(", '%s'", $type);
108              
109 0           my %opt = map { lc($_) => 1 } @opt;
  0            
110              
111 0 0         if (lc($type) =~ /^(enum|set)$/) {
112             # XXX
113 0           $ret .= sprintf(" => ['%s']", join "','", @{$column_info->{MYSQL_VALUES}});
  0            
114             }
115              
116 0 0         $ret .= ", signed" if $opt{signed};
117 0 0 0       $ret .= ", unsigned" if $opt{unsigned} && !$args->{default_unsigned};
118              
119 0 0         if (defined $column_info->column_size) {
120 0           my $column_size;
121              
122 0 0 0       if (lc($type) eq 'decimal') {
    0 0        
    0 0        
    0 0        
    0          
123             # XXX
124 0           $column_size = sprintf("[%d, %d]", $column_info->column_size, $column_info->{DECIMAL_DIGITS});
125             }
126             elsif (lc($type) =~ /^(enum|set)$/) {
127             ;;
128             }
129             # TODO use DBIx::Schema::DSL->context->default_varchar_size
130             elsif (lc($type) eq 'varchar' && $column_info->column_size == 255) {
131             ;;
132             }
133             elsif (
134             lc($type) =~ /^(int|integer)$/ &&
135             (
136             $opt{unsigned} && $column_info->column_size == 10
137             or
138             !$opt{unsigned} && $column_info->column_size == 11
139             )
140             ) {
141             ;;
142             }
143             elsif ($column_info->{MYSQL_TYPE_NAME} && $column_info->{MYSQL_TYPE_NAME} !~ $column_info->column_size) {
144             ;;
145             }
146             else {
147 0           $column_size = $column_info->column_size;
148             }
149              
150              
151 0 0         $ret .= sprintf(", size => %s", $column_size) if $column_size;
152             }
153              
154 0 0         $ret .= ", null" if $column_info->nullable;
155 0 0 0       $ret .= ", not_null" if !$column_info->nullable && !$args->{default_not_null};
156              
157 0 0         if (defined $column_info->column_def) {
158 0           my $column_def = $column_info->column_def;
159              
160 0 0 0       if ($type =~ /^(TIMESTAMP|DATETIME)$/ && $column_def eq 'CURRENT_TIMESTAMP') {
161 0           $ret .= sprintf(", default => \\'%s'", $column_def)
162             }
163             else {
164 0           $ret .= sprintf(", default => '%s'", $column_def)
165             }
166             }
167              
168 0 0 0       if (
      0        
169             $opt{auto_increment} or
170             # XXX
171             ($args->{dbh}->{'Driver'}{'Name'} eq 'mysql' && $column_info->{MYSQL_IS_AUTO_INCREMENT})
172             ) {
173 0           $ret .= ", auto_increment"
174             }
175              
176 0           $ret .= ";\n";
177              
178 0           return $ret;
179             }
180              
181             # FIXME not supported UPDATE_RULE, DELETE_RULE
182             sub _render_index {
183 0     0     my ($table_info, $args) = @_;
184              
185 0           my $ret = "";
186              
187             # index
188 0           my $ret_primary_key = "";
189 0           my $ret_index_key = "";
190 0           my $ret_foreign_key = "";
191              
192 0           my @fk_list = $table_info->fk_foreign_keys(+{ pk_schema => $table_info->schema });
193 0           for my $fk (@fk_list) {
194              
195 0 0 0       if ($fk->fkcolumn_name eq sprintf('%s_id', $fk->pktable_name)) {
    0 0        
    0          
196 0           $ret_foreign_key .= sprintf(" belongs_to '%s';\n", $fk->pktable_name)
197             }
198             elsif ($fk->fkcolumn_name eq 'id' && $fk->pkcolumn_name eq sprintf('%s_id', $fk->fktable_name)) {
199              
200 0           my $itr = _statistics_info($args->{dbh}, $table_info->schema, $fk->pktable_name);
201 0           while (my $index_key = $itr->next) {
202 0 0         if ($index_key->column_name eq $fk->pkcolumn_name) {
203 0 0         my $has = $index_key->non_unique ? 'has_many' : 'has_one';
204 0           $ret_foreign_key .= sprintf(" %s '%s'\n", $has, $fk->pktable_name);
205 0           last;
206             }
207             }
208             }
209             elsif ($fk->pktable_name && $fk->pkcolumn_name) {
210 0           $ret_foreign_key .= sprintf(" foreign_key '%s' => '%s','%s'\n", $fk->fkcolumn_name, $fk->pktable_name, $fk->pkcolumn_name);
211             }
212             }
213              
214 0           my %fkcolumn_map = map { $_->fkcolumn_name => $_ } @fk_list;
  0            
215 0           my %statistics_info_map;
216 0           my $statistics_info = _statistics_info($args->{dbh}, $table_info->schema, $table_info->name);
217 0           while (my $statistics = $statistics_info->next) {
218 0           push @{$statistics_info_map{$statistics->index_name}} => $statistics;
  0            
219             }
220              
221 0           for my $index_name (sort keys %statistics_info_map) {
222 0           my @statistics_list = @{$statistics_info_map{$index_name}};
  0            
223 0           my @column_names = map { $_->column_name } sort { $a->{ORDINAL_POSITION} <=> $b->{ORDINAL_POSITION} } @statistics_list;
  0            
  0            
224              
225 0 0         if (lc($index_name) eq 'primary') {
226 0           $ret_primary_key .= sprintf(" set_primary_key '%s';\n", join "','", @column_names);
227             }
228             else {
229             # fkcolumn is index automatically
230 0 0 0       next if @column_names == 1 && $fkcolumn_map{$column_names[0]};
231              
232 0           $ret_index_key .= sprintf(" add_%sindex '%s' => [%s]%s;\n",
233             $statistics_list[0]->non_unique ? '' : 'unique_',
234             $index_name,
235 0 0 0       (join ",", (map { q{'}.$_.q{'} } @column_names)),
    0          
236             $statistics_list[0]->non_unique &&
237             $statistics_list[0]->type && lc($statistics_list[0]->type) ne 'btree' ? sprintf(", '%s'", $statistics_list[0]->type) : '',
238             );
239             }
240             }
241              
242 0 0 0       if ($ret_primary_key or $ret_index_key or $ret_foreign_key) {
      0        
243 0           $ret .= "\n";
244 0 0         $ret .= $ret_primary_key if $ret_primary_key;
245 0 0         $ret .= $ret_index_key if $ret_index_key;
246 0 0         $ret .= $ret_foreign_key if $ret_foreign_key;
247             }
248              
249 0           return $ret;
250             }
251              
252             # EXPERIMENTAL: https://metacpan.org/pod/DBI#statistics_info
253             sub _statistics_info {
254 0     0     my ($dbh, $schema, $table_name) = @_;
255              
256 0           my $sth;
257 0 0         if ($dbh->{'Driver'}{'Name'} eq 'mysql') {
258             # TODO p-r DBD::mysqld ??
259 0           my $sql = q{
260             SELECT
261             TABLE_CATALOG AS TABLE_CAT,
262             TABLE_SCHEMA AS TABLE_SCHEM,
263             TABLE_NAME,
264             NON_UNIQUE,
265             NULL AS INDEX_QUALIFIER,
266             INDEX_NAME,
267             INDEX_TYPE AS TYPE,
268             SEQ_IN_INDEX AS ORDINAL_POSITION,
269             COLUMN_NAME,
270             NULL AS ASC_OR_DESC,
271             CARDINALITY,
272             NULL AS PAGES,
273             NULL AS FILTER_CONDITION,
274              
275             SUB_PART,
276             PACKED,
277             NULLABLE,
278             INDEX_TYPE,
279             COMMENT
280             FROM
281             INFORMATION_SCHEMA.STATISTICS
282             WHERE
283             table_schema = ?
284             AND table_name = ?
285             };
286 0           $sth = $dbh->prepare($sql);
287 0           $sth->execute($schema, $table_name);
288             }
289             else {
290 0           $sth = $dbh->statistics_info(undef, undef, $table_name, undef, undef);
291             }
292              
293             DBIx::Inspector::Iterator->new(
294             sth => $sth,
295             callback => sub {
296             # TODO p-r DBIx::Inspector ??
297 0     0     my $row = shift;
298 0           DBIx::Inspector::Statics->new($row);
299             },
300 0           );
301             }
302              
303             package # hide from PAUSE
304             DBIx::Inspector::Statics;
305              
306             sub new {
307 0     0     my $class = shift;
308 0 0         my %args = @_ == 1 ? %{ $_[0] } : @_;
  0            
309 0           bless {%args}, $class;
310             }
311              
312             {
313 1     1   8 no strict 'refs';
  1         2  
  1         116  
314             for my $k (
315             qw/
316             TABLE_CAT
317             TABLE_SCHEM
318             TABLE_NAME
319             NON_UNIQUE
320             INDEX_QUALIFIER
321             INDEX_NAME
322             TYPE
323             ORDINAL_POSITION
324             COLUMN_NAME
325             ASC_OR_DESC
326             CARDINALITY
327             PAGES
328             FILTER_CONDITION
329             /
330             )
331             {
332 0     0     *{ __PACKAGE__ . "::" . lc($k) } = sub { $_[0]->{$k} };
333             }
334             }
335              
336              
337             1;
338             __END__