File Coverage

blib/lib/Xtract/Scan.pm
Criterion Covered Total %
statement 79 94 84.0
branch 23 36 63.8
condition 7 15 46.6
subroutine 9 12 75.0
pod 0 7 0.0
total 118 164 71.9


line stmt bran cond sub pod time code
1             package Xtract::Scan;
2              
3 4     4   81 use 5.008005;
  4         13  
  4         263  
4 4     4   25 use strict;
  4         9  
  4         146  
5 4     4   24 use warnings;
  4         9  
  4         147  
6 4     4   31 use Carp ();
  4         18  
  4         71  
7 4     4   22 use Params::Util ();
  4         9  
  4         5007  
8              
9             our $VERSION = '0.16';
10              
11              
12              
13              
14              
15             ######################################################################
16             # Class Methods
17              
18             # Scanner factory
19             sub create {
20 4     4 0 16 my $class = shift;
21 4         7 my $dbh = shift;
22 4         66 my $name = $dbh->{Driver}->{Name};
23 4 50       154 my $driver = Params::Util::_DRIVER("Xtract::Scan::$name", 'Xtract::Scan')
24             or Carp::croak('No driver for the database handle');
25 4         743 $driver->new( dbh => $dbh );
26             }
27              
28              
29              
30              
31              
32             ######################################################################
33             # Constructor and Accessors
34              
35             sub new {
36 4     4 0 8 my $class = shift;
37 4         18 my $self = bless { @_ }, $class;
38              
39             # Check params
40 4 50       25 unless ( Params::Util::_INSTANCE($self->dbh, 'DBI::db') ) {
41 0         0 Carp::croak("Param 'dbh' is not a 'DBI::db' object");
42             }
43              
44 4         18 return $self;
45             }
46              
47             sub dbh {
48 10     10 0 112 $_[0]->{dbh};
49             }
50              
51              
52              
53              
54              
55             ######################################################################
56             # Database Introspection
57              
58             sub tables {
59 0     0 0 0 $_[0]->dbh->tables;
60             }
61              
62             sub columns {
63 0     0 0 0 $_[0]->dbh->column_info
64             }
65              
66              
67              
68              
69             ######################################################################
70             # Generators
71              
72             # Generic ANSI add table fallback
73             sub add_table {
74 0     0 0 0 my $self = shift;
75 0         0 my $table = shift;
76 0   0     0 my $from = shift || $table->name;
77 0         0 return $self->add_select(
78             $table,
79             "SELECT * FROM $from",
80             );
81             }
82              
83             # Generic ANSI add select
84             sub add_select {
85 1     1 0 3 my $self = shift;
86 1         2 my $tname = shift;
87 1         2 my $select = shift;
88 1         4 my @params = @_;
89              
90             # Make an initial scan pass over the query and do a content-based
91             # classification of the data in each column.
92 1         3 my @names = ();
93 1         3 my @type = ();
94 1         2 my @bind = ();
95 1         5 SCOPE: {
96 1         2 my $sth = $self->dbh->prepare($select);
97 1 50       68 unless ( $sth ) {
98 0         0 croak($DBI::errstr);
99             }
100 1         104 $sth->execute( @params );
101 1         3 @names = map { lc($_) } @{$sth->{NAME}};
  2         8  
  1         12  
102 1         6 foreach ( @names ) {
103 2         20 push @type, {
104             NULL => 0,
105             NOTNULL => 0,
106             NUMBER => 0,
107             INTEGER => 0,
108             INTMIN => undef,
109             INTMAX => undef,
110             TEXT => 0,
111             UNIQUE => {},
112             };
113             }
114 1         3 my $rows = 0;
115 1         16 while ( my $row = $sth->fetchrow_arrayref ) {
116 3         6 $rows++;
117 3         7 foreach my $i ( 0 .. $#names ) {
118 6         13 my $value = $row->[$i];
119 6         7 my $hash = $type[$i];
120 6 50       15 if ( defined $value ) {
121 6         8 $hash->{NOTNULL}++;
122 6 100 66     30 if ( $i == 0 and $hash->{UNIQUE} ) {
123 3         19 $hash->{UNIQUE}->{$value}++;
124             }
125             } else {
126 0         0 $hash->{NULL}++;
127 0         0 delete $hash->{UNIQUE};
128 0         0 next;
129             }
130 6 100       187 if ( Params::Util::_NONNEGINT($value) ) {
131 3         67 $hash->{INTEGER}++;
132 3 100 66     19 if ( not defined $hash->{INTMIN} or $value < $hash->{INTMIN} ) {
133 1         3 $hash->{INTMIN} = $value;
134             }
135 3 50 66     19 if ( not defined $hash->{INTMAX} or $value > $hash->{INTMAX} ) {
136 3         7 $hash->{INTMAX} = $value;
137             }
138             }
139 6 100       47 if ( defined Params::Util::_NUMBER($value) ) {
140 3         8 $hash->{NUMBER}++;
141             }
142 6 50       30 if ( length($value) <= 255 ) {
143 6         46 $hash->{TEXT}++;
144             }
145             }
146             }
147 1         6 $sth->finish;
148              
149 1         3 my $col = 0;
150 1         4 foreach my $i ( 0 .. $#names ) {
151             # Initially, assume this isn't a blob
152 2         5 push @bind, 0;
153 2         4 my $hash = $type[$i];
154 2 50       9 my $notnull = $hash->{NULL} ? 'NULL' : 'NOT NULL';
155 2 50       16 if ( $hash->{NOTNULL} == 0 ) {
    100          
    50          
    50          
156             # The column is completely null, no affinity
157 0         0 $type[$i] = "$names[$i] NONE NULL";
158             } elsif ( $hash->{INTEGER} == $hash->{NOTNULL} ) {
159 1         4 $type[$i] = "$names[$i] INTEGER $notnull";
160 1 50 33     10 if ( $i == 0 and $hash->{UNIQUE} ) {
161 1         2 my $d = scalar keys %{$hash->{UNIQUE}};
  1         4  
162 1 50       5 if ( $d == $hash->{NOTNULL} ) {
163 1         7 $type[$i] .= ' PRIMARY KEY';
164             }
165             }
166             } elsif ( $hash->{NUMBER} == $hash->{NOTNULL} ) {
167             # This isn't entirely accurate but should be close enough
168 0         0 $type[$i] = "$names[$i] REAL $notnull";
169             } elsif ( $hash->{TEXT} == $hash->{NOTNULL} ) {
170 1         19 $type[$i] = "$names[$i] TEXT $notnull";
171             } else {
172             # For now lets assume this is a blob
173 0         0 $type[$i] = "$names[$i] BLOB $notnull";
174              
175             # This is a blob after all
176 0         0 $bind[-1] = 1;
177             }
178             }
179             }
180              
181             return (
182 2         13 create => [
183             "CREATE TABLE $tname (\n"
184 2         8 . join(",\n", map { "\t$_" } @type)
185             . "\n)"
186             ],
187             select => [
188             $select,
189             @params,
190             ],
191             insert => (
192             "INSERT INTO $tname VALUES ( "
193             . join( ", ",
194 2         18 map { '?' } @names
195             )
196             . " )",
197             ),
198 1 50       6 blobs => scalar( grep { $_ } @bind ) ? \@bind : undef,
199             );
200             }
201              
202             1;