File Coverage

blib/lib/DBIx/Schema/DSL/Dumper.pm
Criterion Covered Total %
statement 24 152 15.7
branch 0 76 0.0
condition 0 42 0.0
subroutine 8 16 50.0
pod 0 1 0.0
total 32 287 11.1


line stmt bran cond sub pod time code
1             package DBIx::Schema::DSL::Dumper;
2 1     1   578 use 5.008001;
  1         3  
  1         32  
3 1     1   4 use strict;
  1         1  
  1         24  
4 1     1   4 use warnings;
  1         7  
  1         26  
5 1     1   432 use DBIx::Inspector;
  1         9735  
  1         48  
6 1     1   11 use DBIx::Inspector::Iterator;
  1         2  
  1         30  
7 1     1   7 use Carp ();
  1         1  
  1         72  
8              
9             our $VERSION = "0.06";
10              
11             # XXX copy from SQL::Translator::Parser::DBI-1.59
12 1         2395 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   8 };
  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             sub _render_index {
182 0     0     my ($table_info, $args) = @_;
183              
184 0           my @primary_key_names = map { $_->name } $table_info->primary_key;
  0            
185 0           my @fk_list = $table_info->fk_foreign_keys;
186 0           my %statistics_info_map = map {
187 0           $_->column_name => $_;
188             } _statistics_info($args->{dbh}, $table_info->schema, $table_info->name)->all;
189              
190 0           my $ret = "";
191              
192             # primary key
193 0 0         if (@primary_key_names) {
194 0           delete $statistics_info_map{$_} for @primary_key_names;
195              
196 0           $ret .= "\n";
197 0           $ret .= sprintf(" set_primary_key('%s');\n", join "','", @primary_key_names);
198             }
199              
200             # foreign key && stash index_info
201 0           my %index_info;
202 0           my $ret_foreign_key = "";
203 0           for my $fk (@fk_list) {
204 0           my $index_key = delete $statistics_info_map{$fk->fkcolumn_name};
205              
206             # FIXME not supported UPDATE_RULE, DELETE_RULE
207 0 0 0       if ($fk->pktable_name && $fk->fkcolumn_name eq sprintf('%s_id', $fk->pktable_name)) {
    0 0        
    0 0        
      0        
208 0           $ret_foreign_key .= sprintf(" belongs_to('%s')\n", $fk->pktable_name)
209             }
210             elsif ($fk->fkcolumn_name eq 'id' && $fk->pkcolumn_name eq sprintf('%s_id', $fk->fktable_name)) {
211              
212 0           my $itr = _statistics_info($args->{dbh}, $table_info->schema, $fk->pktable_name);
213 0           while (my $index_key = $itr->next) {
214 0 0         if ($index_key->column_name eq $fk->pkcolumn_name) {
215 0 0         my $has = $index_key->non_unique ? 'has_many' : 'has_one';
216 0           $ret_foreign_key .= sprintf(" %s('%s')\n", $has, $fk->pktable_name);
217 0           last;
218             }
219             }
220             }
221             elsif ($fk->fkcolumn_name && $fk->pktable_name && $fk->pkcolumn_name) {
222 0           $ret_foreign_key .= sprintf(" foreign_key('%s','%s','%s')\n", $fk->fkcolumn_name, $fk->pktable_name, $fk->pkcolumn_name);
223             }
224             else {
225 0           push @{$index_info{$index_key->index_name}} => $index_key;
  0            
226             }
227             }
228              
229 0           push @{$index_info{$_->index_name}} => $_ for values %statistics_info_map;
  0            
230              
231 0           for my $index_name (sort keys %index_info) {
232 0           my @index_keys = @{$index_info{$index_name}};
  0            
233 0           my @column_names = map { $_->column_name } @index_keys;
  0            
234              
235 0           $ret .= sprintf(" add_%sindex('%s' => [%s]%s);\n",
236             $index_keys[0]->non_unique ? '' : 'unique_',
237             $index_name,
238 0 0 0       (join ",", (map { q{'}.$_.q{'} } @column_names)),
    0          
239             $index_keys[0]->non_unique && $index_keys[0]->type ? sprintf(", '%s'", $index_keys[0]->type) : '',
240             );
241             }
242              
243 0 0         if ($ret_foreign_key) {
244 0           $ret .= $ret_foreign_key;
245             }
246              
247 0           return $ret;
248             }
249              
250             # EXPERIMENTAL: https://metacpan.org/pod/DBI#statistics_info
251             sub _statistics_info {
252 0     0     my ($dbh, $schema, $table_name) = @_;
253              
254 0           my $sth;
255 0 0         if ($dbh->{'Driver'}{'Name'} eq 'mysql') {
256             # TODO p-r DBD::mysqld ??
257 0           my $sql = q{
258             SELECT
259             *
260             FROM
261             INFORMATION_SCHEMA.STATISTICS
262             WHERE
263             table_schema = ?
264             AND table_name = ?
265             };
266 0           $sth = $dbh->prepare($sql);
267 0           $sth->execute($schema, $table_name);
268             }
269             else {
270 0           $sth = $dbh->statistics_info(undef, undef, $table_name, undef, undef);
271             }
272              
273             DBIx::Inspector::Iterator->new(
274             sth => $sth,
275             callback => sub {
276             # TODO p-r DBIx::Inspector ??
277 0     0     my $row = shift;
278 0           DBIx::Inspector::Statics->new($row);
279             },
280 0           );
281             }
282              
283             package # hide from PAUSE
284             DBIx::Inspector::Statics;
285              
286             sub new {
287 0     0     my $class = shift;
288 0 0         my %args = @_ == 1 ? %{ $_[0] } : @_;
  0            
289 0           bless {%args}, $class;
290             }
291              
292             {
293 1     1   9 no strict 'refs';
  1         1  
  1         147  
294             for my $k (
295             qw/
296             TABLE_CAT
297             TABLE_SCHEM
298             TABLE_NAME
299             NON_UNIQUE
300             INDEX_QUALIFIER
301             INDEX_NAME
302             TYPE
303             ORDINAL_POSITION
304             COLUMN_NAME
305             ASC_OR_DESC
306             CARDINALITY
307             PAGES
308             FILTER_CONDITION
309             /
310             )
311             {
312 0     0     *{ __PACKAGE__ . "::" . lc($k) } = sub { $_[0]->{$k} };
313             }
314             }
315              
316              
317             1;
318             __END__