File Coverage

blib/lib/Mojar/Mysql/Util.pm
Criterion Covered Total %
statement 11 71 15.4
branch 2 44 4.5
condition n/a
subroutine 3 6 50.0
pod 1 2 50.0
total 17 123 13.8


line stmt bran cond sub pod time code
1             package Mojar::Mysql::Util;
2 1     1   1368 use Mojo::Base -strict;
  1         3  
  1         6  
3              
4             our $VERSION = 0.011;
5              
6             require Carp;
7              
8             sub import {
9 1     1   11 my $caller = caller;
10 1         2 my %want = map {$_ => 1} @_;
  1         5  
11              
12 1     1   170 no strict 'refs';
  1         4  
  1         728  
13 0         0 *{"${caller}::find_monotonic_first"} = \&find_monotonic_first
14 1 50       5 if $want{find_monotonic_first};
15              
16             # Need a closure for lookup => cannot use Exporter
17 1 50   0   9 *{"${caller}::lookup"} = sub { lookup($caller, @_) } if $want{lookup};
  0            
  0            
18             }
19              
20             our $FindRangeSize = 200;
21              
22             sub find_monotonic_first {
23 0     0 1   my ($dbh, $schema, $table, $column, $condition) = @_;
24 0 0         Carp::croak $column .'Missing required condition' unless defined $condition;
25 0           my $debug = $ENV{MOJAR_MYSQL_UTIL_DEBUG};
26              
27 0           my ($min, $max) = $dbh->selectrow_array(sprintf
28             q{SELECT MIN(%s), MAX(%s)
29             FROM %s.%s},
30             $column, $column,
31             $schema, $table
32             );
33              
34 0 0         if (ref $condition eq 'CODE') {
35             # Perl callback
36 0           my $sth_row = $dbh->prepare(sprintf
37             q{SELECT *
38             FROM %s.%s
39             WHERE %s = ?},
40             $schema, $table,
41             $column
42             );
43              
44 0           my $row;
45 0           do {
46             # Check minimum
47 0           $row = $dbh->selectrow_hashref($sth_row, undef, $min);
48 0 0         return $min if $condition->($row);
49              
50             # Check maximum
51 0           $row = $dbh->selectrow_hashref($sth_row, undef, $max);
52 0 0         return undef unless $condition->($row); # Problem with data
53              
54             # Check range
55 0 0         if ($max - $min <= $FindRangeSize) {
56 0           my $candidate = $min;
57 0           do {
58 0           ($candidate) = $dbh->selectrow_array(sprintf(
59             q{SELECT MIN(%s)
60             FROM %s.%s
61             WHERE ? < %s},
62             $column,
63             $schema, $table,
64             $column),
65             undef,
66             $candidate
67             );
68 0           $row = $dbh->selectrow_hashref($sth_row, undef, $candidate);
69             } until $condition->($row);
70 0           return $candidate;
71             }
72              
73             # Calculate new
74             # First find mean
75 0           my $new = $min + int( ($max - $min) / 2 + 0.1 );
76             # then find first record after that...
77 0           my ($candidate) = $dbh->selectrow_array(sprintf(
78             q{SELECT MIN(%s)
79             FROM %s.%s
80             WHERE ? <= %s},
81             $column,
82             $schema, $table,
83             $column),
84             undef,
85             $new
86             );
87 0 0         if ($candidate >= $max) {
88             # ...or before that
89 0           ($candidate) = $dbh->selectrow_array(sprintf(
90             q{SELECT MAX(%s)
91             FROM %s.%s
92             WHERE %s <= ?},
93             $column,
94             $schema, $table,
95             $column),
96             undef,
97             $new
98             );
99 0 0         return undef if $candidate <= $min; # Problem with data
100             }
101 0           $new = $candidate;
102             # $min < $candidate < $max
103              
104 0           $row = $dbh->selectrow_hashref($sth_row, undef, $new);
105 0 0         if ($condition->($row)) {
106 0           $max = $new;
107             }
108             else {
109 0           $min = $new;
110             }
111 0 0         warn $min, ' : ', $max if $debug;
112             } while 1;
113             }
114              
115             else {
116             # SQL where-clause
117 0           my $sth_row = $dbh->prepare(sprintf
118             q{SELECT COUNT(*)
119             FROM %s.%s
120             WHERE %s = ?
121             AND (%s)},
122             $schema, $table,
123             $column,
124             $condition
125             );
126 0           my $sth_range = $dbh->prepare(sprintf
127             q{SELECT MIN(%s)
128             FROM %s.%s
129             WHERE ? <= %s
130             AND %s <= ?
131             AND (%s)},
132             $column,
133             $schema, $table,
134             $column,
135             $column,
136             $condition
137             );
138              
139             # Brute force (for demos)
140             # return $dbh->selectrow_arrayref($sth_range, undef, $min, $max)->[0];
141              
142 0           my $satisfied;
143 0           do {
144             # Check range
145 0 0         if ($max - $min <= $FindRangeSize) {
146 0           my ($solution) = $dbh->selectrow_array($sth_range, undef, $min, $max);
147 0           return $solution;
148             }
149              
150             # Check minimum
151 0           ($satisfied) = $dbh->selectrow_array($sth_row, undef, $min);
152 0 0         return $min if $satisfied;
153              
154             # Check maximum
155 0           ($satisfied) = $dbh->selectrow_array($sth_row, undef, $max);
156 0 0         return undef unless $satisfied; # Problem with data
157              
158             # Calculate new
159             # First find mean
160 0           my $new = $min + int( ($max - $min) / 2 + 0.1 );
161             # then find first record after that...
162 0           my ($candidate) = $dbh->selectrow_array(sprintf(
163             q{SELECT MIN(%s)
164             FROM %s.%s
165             WHERE ? <= %s},
166             $column,
167             $schema, $table,
168             $column),
169             undef,
170             $new
171             );
172 0 0         if ($candidate >= $max) {
173             # ...or before that
174 0           ($candidate) = $dbh->selectrow_array(sprintf(
175             q{SELECT MAX(%s)
176             FROM %s.%s
177             WHERE %s <= ?},
178             $column,
179             $schema, $table,
180             $column),
181             undef,
182             $new
183             );
184 0 0         return undef if $candidate <= $min; # Problem with data
185             }
186 0           $new = $candidate;
187             # $min < $candidate < $max
188              
189 0           ($satisfied) = $dbh->selectrow_array($sth_row, undef, $new);
190 0 0         if ($satisfied) {
191 0           $max = $new;
192             }
193             else {
194 0           $min = $new;
195             }
196 0 0         warn $min, ' : ', $max if $debug;
197             } while 1;
198             }
199             }
200              
201             sub lookup {
202 0     0 0   my ($class, $name, $schema, $table, $key_col, $value_col) = @_;
203 0 0         Carp::croak 'Wrong number of args' unless @_ == 6;
204 0 0         Carp::croak qq{Lookup '$name' invalid} unless $name =~ /^[a-zA-Z_]\w*$/;
205              
206 0           my $code = <
207             package $class;
208             sub $name {
209             my \$dbh = shift;
210             if (\@_ == 1) {
211             return \$dbh->selectrow_arrayref(
212             q{SELECT $value_col
213             FROM ${schema}.$table
214             WHERE $key_col = ?},
215             undef,
216             \$_[0]
217             )->[0];
218             }
219             \$dbh->do(
220             q{REPLACE INTO ${schema}.$table
221             SET $value_col = ?
222             WHERE $key_col = ?},
223             undef,
224             \$_[1], \$_[0]
225             );
226             return;
227             }
228             EOT
229 0 0         warn "-- Lookup $name in $class\n$code\n\n" if $ENV{MOJAR_MYSQL_UTIL_DEBUG};
230 0 0         Carp::croak "Mojar::Mysql::Util error: $@" unless eval "$code;1";
231             }
232              
233             1;
234             __END__