File Coverage

blib/lib/Text/ANSITable/SQLStyleSheet.pm
Criterion Covered Total %
statement 92 99 92.9
branch 19 28 67.8
condition 1 3 33.3
subroutine 15 15 100.0
pod 1 1 100.0
total 128 146 87.6


line stmt bran cond sub pod time code
1             package Text::ANSITable::SQLStyleSheet;
2 3     3   74828 use 5.018000;
  3         25  
3 3     3   17 use strict;
  3         9  
  3         59  
4 3     3   15 use warnings;
  3         6  
  3         70  
5 3     3   1387 use version;
  3         6217  
  3         17  
6 3     3   2645 use Text::ANSITable;
  3         312707  
  3         146  
7 3     3   2118 use JSON;
  3         31184  
  3         23  
8 3     3   5440 use DBI;
  3         54150  
  3         214  
9 3     3   2591 use DBD::SQLite;
  3         29531  
  3         3519  
10              
11             our $VERSION = '0.05';
12              
13             our $Json = JSON->new;
14              
15             sub _sqlite_type_from_sth_ix {
16              
17 2     2   8 my ($dbh, $sth, $ix) = @_;
18              
19 2         37 my $sth_type = $sth->{TYPE}[ $ix ];
20              
21             # NOTE: It seems that as of 2018-10-06 DBD::SQLite returns the
22             # wrong kind of value for TYPE. It also does not implement the
23             # type_info method.
24             #
25             # Worse, for something like `SELECT 1, TYPEOF(1)` SQLite itself
26             # considers the first to be an `integer`, but DBD::SQLite returns
27             # `VARCHAR` in $sth->{TYPE}.
28             #
29             # Reported in .
30             #
31             # Other drivers may have similar issues, so this is best-effort
32             # guesswork.
33             #
34             # SQLite will do further guesswork on the type we pass to it, per
35             # .
36              
37 2 50       14 if (defined $sth_type) {
38              
39 2         23 my $type_info = $dbh->type_info($sth_type);
40              
41 2 50 33     62 if ( $type_info and exists $type_info->{TYPE_NAME} ) {
    50          
42              
43 0         0 return $type_info->{TYPE_NAME};
44              
45             } elsif ($sth_type !~ /^\d+$/) {
46              
47 2         31 return $sth_type;
48              
49             }
50              
51             }
52              
53 0         0 return 'TEXT';
54              
55             }
56              
57             sub _from_sth_sql {
58              
59 2     2   8 my ($class, $sth, $sql_or_code) = @_;
60              
61 2         10 my $dbh = DBI->connect('dbi:SQLite:dbname=:memory:');
62              
63 2         907 local $dbh->{sqlite_allow_multiple_statements} = 1;
64              
65 2         52 $dbh->do(q{
66             PRAGMA synchronous = OFF;
67             PRAGMA journal_mode = OFF;
68             PRAGMA locking_mode = EXCLUSIVE;
69             });
70              
71             my $fields = join(',', map {
72             sprintf(
73             'CAST(NULL AS %s) AS %s',
74             _sqlite_type_from_sth_ix($dbh, $sth, $_),
75 2         43 $dbh->quote_identifier(undef, undef, $sth->{NAME}[$_]))
76 2         246 } 0 .. @{ $sth->{NAME} } - 1);
  2         47  
77              
78 2         270 my $create_sth = $dbh->prepare(sprintf(q{
79             CREATE TABLE data AS SELECT %s LIMIT 0
80             }, $fields));
81              
82 2         451 $create_sth->execute();
83              
84             my $insert_sth = $dbh->prepare(sprintf(q{
85             INSERT INTO data VALUES(%s)
86 2         17 }, join(',', map { '?' } 0 .. @{ $sth->{NAME} } - 1)));
  2         23  
  2         27  
87              
88 2         176 $dbh->begin_work;
89              
90 2         74 while (my $row = $sth->fetchrow_arrayref()) {
91 20         219 $insert_sth->execute(@$row);
92             }
93              
94 2         24 $dbh->commit;
95              
96             # $dbh->sqlite_backup_to_file('DEBUG.sqlite');
97              
98 2 100       12 if ( 'CODE' eq ref( $sql_or_code ) ) {
99              
100 1         4 return $class->from_sth( $sql_or_code->($dbh) );
101              
102             } else {
103              
104 1         6 my $final_sth = $dbh->prepare( $sql_or_code );
105 1         158 $final_sth->execute();
106 1         11 return $class->from_sth( $final_sth );
107              
108             }
109              
110             }
111              
112             sub from_sth {
113              
114 4     4 1 4723 my ($class, $sth, $sql_or_code) = @_;
115              
116 4 100       23 return _from_sth_sql(@_) if defined $sql_or_code;
117              
118 2         4 my %col2ix;
119             $col2ix{$_} = keys %col2ix
120 2         6 for @{ $sth->{NAME} };
  2         34  
121              
122             # filter out __*_style columns
123 0         0 my @wanted = sort { $a <=> $b } map {
124 2 100       12 /^__(\w+)_style$/ ? () : $col2ix{$_}
  6         47  
125             } keys %col2ix;
126              
127 2         24 my $t = Text::ANSITable->new;
128              
129 2         214828 $t->columns([ map { $sth->{NAME}[$_] } @wanted ]);
  2         198  
130              
131 2         61 my $nth = 0;
132            
133 2         188 while (my $row = $sth->fetchrow_arrayref) {
134              
135 20         166 $t->add_row([ @{ $row }[ @wanted ] ]);
  20         108  
136            
137 20 100       295 if ( $nth == 0 ) {
138 2         33 _column_style( $t, $row, \%col2ix, $col2ix{__column_style} );
139             }
140            
141 20         69 _row_style( $t, $nth, $row, \%col2ix, $col2ix{__row_style} );
142 20         478 _cell_styles( $t, $nth, $row, \%col2ix, $col2ix{__cell_style} );
143            
144 20         643 $nth += 1;
145              
146             }
147            
148 2         647 return $t;
149              
150             }
151              
152             sub _get_hashref {
153 42     42   80 my ($row, $ix) = @_;
154              
155 42 100       100 return unless defined $ix;
156 40 50       79 return unless defined $row->[ $ix ];
157              
158 40         327 my $h = $Json->decode( $row->[ $ix ] );
159              
160 40 50       109 return unless 'HASH' eq ref( $h );
161              
162 40         148 delete @$h{ grep !defined($h->{$_}), keys %$h };
163            
164 40         108 return $h;
165              
166             }
167              
168             sub _column_style {
169            
170 2     2   16 my ($t, $row, $col2ix, $ix) = @_;
171              
172 2 50       10 return unless my $style = _get_hashref( $row, $ix );
173              
174 0         0 for my $column ( grep { exists $col2ix->{$_} } keys %$style ) {
  0         0  
175 0         0 $t->set_column_style( $column, %{ $style->{ $column } } );
  0         0  
176             }
177              
178             }
179              
180             sub _row_style {
181            
182 20     20   49 my ($t, $nth_row, $row, $col2ix, $ix) = @_;
183              
184 20 50       37 return unless my $style = _get_hashref( $row, $ix );
185              
186 20         86 $t->set_row_style( $nth_row, %$style );
187              
188             }
189              
190             sub _cell_styles {
191            
192 20     20   48 my ($t, $nth_row, $row, $col2ix, $ix) = @_;
193              
194 20 50       34 return unless my $style = _get_hashref( $row, $ix );
195              
196 20         44 for my $column ( grep { exists $col2ix->{$_} } keys %$style ) {
  20         69  
197              
198 20         31 my %cell_style = %{ $style->{ $column } };
  20         70  
199              
200             # override cell contents if pseudo-style `text` is specified
201 20 50       64 if ( exists $cell_style{value} ) {
202 20         94 $t->set_cell( $nth_row, $column, $cell_style{value} );
203 20         414 delete $cell_style{value};
204             }
205              
206             $t->set_cell_style(
207 20         63 $nth_row,
208             $column,
209             %cell_style
210             );
211              
212             }
213              
214             }
215              
216             1;
217              
218             __END__