File Coverage

blib/lib/SQL/Interpolate/Macro.pm
Criterion Covered Total %
statement 232 251 92.4
branch 68 84 80.9
condition 23 45 51.1
subroutine 42 49 85.7
pod 8 10 80.0
total 373 439 84.9


line stmt bran cond sub pod time code
1             package SQL::Interpolate::Macro;
2 4     4   3024 use strict;
  4         9  
  4         138  
3 4     4   21 use warnings;
  4         7  
  4         116  
4 4     4   19 use base qw(Exporter);
  4         6  
  4         325  
5 4     4   19 use SQL::Interpolate;
  4         9  
  4         23  
6              
7             BEGIN {
8 4     4   15 SQL::Interpolate::_enable_macros();
9             }
10              
11             our $VERSION = '0.32';
12             our @EXPORT;
13             our %EXPORT_TAGS = (all => [qw(
14             sql_and
15             sql_flatten
16             sql_if
17             sql_link
18             sql_or
19             sql_paren
20             sql_rel
21             sql_rel_filter
22              
23             relations
24             sql_fragment
25             )]); # note: relations and sql_fragment depreciated
26             our @EXPORT_OK = @{$EXPORT_TAGS{'all'}};
27              
28             sub sql_flatten;
29              
30             sub sql_flatten {
31 490     490 1 2971 my (@items) = @_;
32              
33             # extract optional state parameter
34 490         559 my $state;
35             my $interp;
36 490 50       2296 if (ref $items[0] eq 'DBI::db') {
    100          
37 0         0 $state = shift @items;
38             }
39             elsif (UNIVERSAL::isa($items[0], 'SQL::Interpolate')) {
40 242         372 $state = $interp = shift @items;
41             }
42              
43             # flatten items
44 886         1302 @items = map {
45 490         793 my $e = $_;
46 886 100       9360 if (UNIVERSAL::isa($e, 'SQL::Interpolate::Macro')) {
    100          
    100          
    100          
47 63         123 my @out = $e->expand($state);
48 63   66     261 sql_flatten $state || (), @out;
49             }
50             elsif (ref $e eq 'SQL::Interpolate::SQL') {
51 101   66     484 sql_flatten $state || (), @$e;
52             }
53             elsif (ref $e eq 'ARRAY') {
54 55         76 my $complex = 0;
55 55 100       96 for my $o (@$e) { ref $o ne '' and do { $complex = 1; last }; }
  88         221  
  16         22  
  16         21  
56 55 100       108 if ($complex) {
57 16         19 my @newarray;
58 16         24 for my $o (@$e) {
59 32 50 33     179 if (UNIVERSAL::isa($o, 'SQL::Interpolate::Macro')) {
    100          
    50          
60 0         0 push @newarray, $o->expand($state);
61             }
62             elsif (ref $o eq 'SQL::Interpolate::SQL') {
63 16   66     68 push @newarray,
64             SQL::Interpolate::SQL->new(
65             sql_flatten $state || (), @$o);
66             }
67             elsif (ref $o eq '' or
68             ref $o eq 'SQL::Interpolate::Variable')
69             {
70 16         31 push @newarray, $o;
71             }
72             else {
73 0         0 my $type = ref $o;
74 0         0 _error(qq(reference type "$type" not allowed in array.));
75             }
76             }
77 16         54 \@newarray;
78             }
79             else {
80 39         192 $e;
81             }
82             }
83             elsif (ref $e eq '') { # SQL string
84             # apply any filters to string and expand any new macros
85 460 100 100     1129 if ($interp && @{$interp->{text_fragment_filters}} != 0) {
  242         980  
86 130         204 my @out = ($e);
87 130         193 for my $filter (@{$interp->{text_fragment_filters}}) {
  130         233  
88 130         249 @out = $filter->filter_text_fragment($e);
89 130   66     706 my $same = @out == 1 && ref $out[0] eq '' && $out[0] eq $e;
90 130 100       346 unless($same) {
91 16   33     55 @out = sql_flatten($state || (), @out);
92 16         37 last;
93             }
94             }
95             @out
96 130         554 }
97 330         897 else { $e }
98             }
99 207         636 else { $e }
100             } @items;
101              
102 490         1784 return @items;
103             }
104              
105             sub sql_and {
106 5     5 1 18 return SQL::Interpolate::And->new(@_);
107             }
108              
109             sub sql_or {
110 4     4 1 14 return SQL::Interpolate::Or->new(@_);
111             }
112              
113             sub sql_if {
114 0     0 1 0 return SQL::Interpolate::If->new(@_);
115             }
116              
117             sub sql_rel {
118 0     0 1 0 return SQL::Interpolate::Rel->new(@_);
119             }
120              
121             sub sql_link {
122 0     0 1 0 return SQL::Interpolate::Link->new(@_);
123             }
124              
125             sub sql_paren {
126 2     2 1 14 return SQL::Interpolate::Paren->new(@_);
127             }
128              
129             sub sql_rel_filter {
130 1     1 1 58 return SQL::Interpolate::RelProcessor->new(@_);
131             }
132              
133             # depreciated
134             sub relations {
135 0     0 0 0 print STDERR
136             "SQL::Interpolate::Macro - WARNING: "
137             . "relations() is depreciated. use sql_rel_filter() instead.\n";
138 0         0 return sql_rel_filter(@_);
139             }
140              
141             # [private]
142             # Given instances of two relations, generate the SQL to link them.
143             # For example,
144             # ['Sp', ['S', 'p'], $sales_ord_line] and
145             # ['p', ['p'], $part]
146             # gives "Sp.part_nbr = p.part_nbr".
147             # params:
148             # $e1 - entity 1
149             # $e2 - entity 2
150             # where each instance is a arrayref of an entity relation name, a arrayref of
151             # names of contained entities, and a relation
152             # specification (as passed into C).
153             sub _single_link_sql {
154 21     21   24 my ($e1, $e2) = @_;
155              
156 21         21 my ($left_idx, $right_idx);
157 21 100 100     173 if ($e1->[1]->[0] eq $e2->[1]->[0])
    100 66        
    100 33        
    50 33        
158 6         6 { $left_idx=0; $right_idx=0; }
  6         8  
159             elsif (defined($e2->[1]->[1]) && $e1->[1]->[0] eq $e2->[1]->[1])
160 3         5 { $left_idx=0; $right_idx=1; }
  3         5  
161             elsif (defined($e1->[1]->[1]) && $e1->[1]->[1] eq $e2->[1]->[0])
162 9         10 { $left_idx=1; $right_idx=0; }
  9         11  
163             elsif (defined($e1->[1]->[1]) && defined($e2->[1]->[1]) &&
164             $e1->[1]->[1] eq $e2->[1]->[1])
165 3         7 { $left_idx=1; $right_idx=1; }
  3         6  
166             else {
167 0         0 die "Invalid SQL link [$e1->[0] to $e2->[0]]";
168             }
169              
170 21         66 my $sql = "$e1->[0].$e1->[2]->{key}->[$left_idx ]" . " = " .
171             "$e2->[0].$e2->[2]->{key}->[$right_idx]";
172              
173 21         54 return $sql;
174             }
175              
176             # depreciated
177             sub sql_fragment {
178 0     0 0 0 print STDERR
179             "SQL::Interpolate::Macro - WARNING: " .
180             "sql_fragment() is depreciated. use sql() instead.\n";
181 0         0 return SQL::Interpolate::SQL->new(@_);
182             }
183              
184             1;
185              
186             package SQL::Interpolate::SQLFilter;
187 4     4   26 use strict;
  4         6  
  4         125  
188 4     4   24 use warnings;
  4         8  
  4         166  
189             #IMPROVE: package name?
190              
191             1;
192              
193             package SQL::Interpolate::RelProcessor;
194 4     4   20 use base 'SQL::Interpolate::SQLFilter';
  4         6  
  4         2256  
195 4     4   24 use strict;
  4         7  
  4         655  
196 4     4   31 use warnings;
  4         5  
  4         2384  
197              
198             sub new {
199 1     1   3 my ($class, $relations) = @_;
200 1         13 return bless {
201             relations => $relations,
202             keys => {}
203             }, $class;
204             }
205              
206             sub init {
207 16     16   17 my $self = shift;
208 16         33 $self->{keys} = {};
209 16         68 return;
210             }
211              
212             sub filter_text {
213 16     16   21 my ($self, $sql) = @_;
214 16         17 while (my ($name, $key) = each %{$self->{keys}}) {
  59         187  
215 43         629 $sql =~ s{ (?
  28         112  
216             }
217 16         69 return $sql;
218             }
219              
220             sub filter_text_fragment {
221 130     130   161 my ($self, $sql) = @_;
222 130         141 my @out;
223 130         251 pos($sql) = 0;
224 130         211 my $pos0 = pos($sql);
225 130         352 until ($sql =~ /\G$/gc) {
226 324         341 my $pos1 = pos($sql);
227 324 100       775 if ($sql =~ m{\G \b REL \( (.*?) \)}xsgc) {
    100          
228 36 50       114 push @out, substr($sql, $pos0, $pos1 - $pos0) if $pos1 != $pos0;
229 36         43 $pos0 = pos($sql);
230 36         83 push @out, SQL::Interpolate::Rel->new($1);
231             }
232             elsif ($sql =~ m{\G \b LINK \( (.*?) \)}xsgc) {
233 16 50       51 push @out, substr($sql, $pos0, $pos1 - $pos0) if $pos1 != $pos0;
234 16         18 $pos0 = pos($sql);
235 16         22 my $params = $1;
236 16         53 my @params = split /,/, $params;
237 16         204 s{^\s*|\s*$}{}gs for @params;
238 16         49 push @out, SQL::Interpolate::Link->new(@params);
239             }
240             else {
241 272         931 $sql =~ m{\G.[^RL]*}xsgc;
242             }
243             }
244 130         147 my $pos1 = pos($sql);
245 130 100       369 push @out, substr($sql, $pos0, $pos1 - $pos0) if $pos1 != $pos0;
246 130         392 return @out;
247             }
248              
249             1;
250              
251             package SQL::Interpolate::Rel;
252 4     4   22 use strict;
  4         19  
  4         168  
253 4     4   21 use base 'SQL::Interpolate::Macro';
  4         8  
  4         12198  
254              
255             sub new {
256 36     36   61 my ($class, $name) = @_;
257              
258 36         106 my $self = bless [
259             $name
260             ], $class;
261 36         133 return $self;
262             }
263              
264             sub expand {
265 36     36   46 my ($self, $interp) = @_;
266              
267             # improve-method call?
268 36         53 my $filters = $interp->{filters_hash}->{'SQL::Interpolate::RelProcessor'};
269 36 50       67 die "No sql_rel_filter defined" if ! defined $filters;
270 36 50       67 die "Multiple relation filters currently not supported" if @$filters > 1;
271 36         41 my $filter = $filters->[0];
272              
273 36         46 my $keys = $filter->{keys};
274 36         52 my $name = $self->[0];
275 36         34 my $sql;
276              
277 36         32 for my $relation_name (keys %{$filter->{relations}}) {
  36         104  
278 115         176 my $relation = $filter->{relations}->{$relation_name};
279 115         134 my $name_re = $relation->{name};
280              
281 115 100       1901 if ($name =~ /($name_re)/s) {
282 36         93 my ($name, $name1, $name2) = ($1, $2, $3);
283              
284 36         103 $keys->{$name1} = "$name.$relation->{key}->[0]";
285 36 100       137 $keys->{$name2} = "$name.$relation->{key}->[1]"
286             if defined $relation->{key}->[1];
287 36         56 $sql = "$relation_name as $name";
288 36         76 last;
289             }
290             }
291 36 50       95 if (! defined $sql) {
292 0         0 die "Unrecognized relation REL($name).";
293             }
294 36         94 return $sql;
295             }
296              
297             1;
298              
299             package SQL::Interpolate::Link;
300 4     4   34 use strict;
  4         6  
  4         224  
301 4     4   43 use base 'SQL::Interpolate::Macro';
  4         7  
  4         2274  
302              
303             sub new {
304 16     16   49 my ($class, @rels) = @_;
305              
306 16         49 my $self = bless [
307             @rels
308             ], $class;
309 16         77 return $self;
310             }
311              
312             sub expand {
313 16     16   20 my ($self, $interp) = @_;
314              
315             # improve-method call?
316 16         28 my $filters = $interp->{filters_hash}->{'SQL::Interpolate::RelProcessor'};
317 16 50       31 die "No sql_rel_filter filter defined" if ! defined $filters;
318 16 50       31 die "Multiple relation filters currently not supported" if @$filters > 1;
319 16         17 my $filter = $filters->[0];
320              
321 16         39 my @params = @$self;
322              
323 16         19 my $good = 1;
324 16         16 my $last;
325 16         21 for my $param (@params) {
326 36         40 my $match = 0;
327 36         81 done_param:
328 36         35 for my $relation (values %{$filter->{relations}}) {
329 115         150 my $name_re = $relation->{name};
330 115 100       1650 if ($param =~ /($name_re)/gs) {
331 36         86 my ($name, $name1, $name2) = ($1, $2, $3);
332 36 100       132 $param = [$name, [$name1, defined($name2) ? $name2 : ()],
333             $relation];
334 36         44 $match = 1;
335 36         66 last done_param;
336             }
337             }
338 36 50       101 if (!$match) {
339 0         0 die "Invalid param [$param] in LINK macro in SQL template.";
340             }
341             }
342              
343             # relations touching entities.
344 16         23 my %links;
345             my @sql_snips;
346 16         18 for my $param (@params) {
347 36         52 for my $entity (@{$param->[1]}) {
  36         53  
348 64 100       149 if (defined $links{$entity}) {
349             #print Dumper($entity, $links{$entity}, $param), "\n";
350 21         41 push @sql_snips,
351             SQL::Interpolate::Macro::_single_link_sql(
352             $links{$entity}, $param);
353             }
354             }
355 36         67 $links{$param->[1]->[0]} = $param;
356 36 100       96 $links{$param->[1]->[1]} = $param if defined $param->[1]->[1];
357             }
358              
359 16         33 my $sql = join ' AND ', @sql_snips;
360              
361 16 100       35 $sql = "($sql)" if @sql_snips > 1;
362              
363 16         89 return $sql;
364             }
365              
366             1;
367              
368             package SQL::Interpolate::Paren;
369 4     4   23 use strict;
  4         6  
  4         130  
370 4     4   18 use base 'SQL::Interpolate::Macro';
  4         6  
  4         728  
371              
372             sub new {
373 2     2   4 my ($class, @elements) = @_;
374              
375 2         7 my $self = bless [
376             @elements
377             ], $class;
378 2         14 return $self;
379             }
380              
381             sub expand {
382 2     2   4 my ($self, $interp) = @_;
383 2   33     36 return ('(', SQL::Interpolate::Macro::sql_flatten(
384             $interp || (), @$self), ')');
385             }
386              
387             1;
388              
389             package SQL::Interpolate::And;
390 4     4   21 use strict;
  4         6  
  4         127  
391 4     4   18 use base 'SQL::Interpolate::Macro';
  4         8  
  4         958  
392              
393             sub new {
394 5     5   11 my ($class, @elements) = @_;
395              
396 5         16 my $self = bless [@elements], $class;
397 5         25 return $self;
398             }
399              
400             sub expand {
401 5     5   7 my ($self, $interp) = @_;
402 6   33     66 my @out = map {
403 5         14 my @expand = SQL::Interpolate::Macro::sql_flatten $interp || (), $_;
404 6 100       28 (@expand == 0) ? () : ('AND', '(', @expand, ')')
405             } @$self;
406 5         9 shift @out;
407 5 100       15 return '1=1' if @out == 0; # trivial case
408 2 50       15 @out = ('(', @out, ')') if @out != 0;
409 2         11 return @out;
410             }
411              
412             1;
413              
414             package SQL::Interpolate::Or;
415 4     4   26 use strict;
  4         13  
  4         115  
416 4     4   19 use base 'SQL::Interpolate::Macro';
  4         7  
  4         939  
417              
418             sub new {
419 4     4   6 my ($class, @elements) = @_;
420              
421 4         12 my $self = bless [
422             @elements
423             ], $class;
424 4         25 return $self;
425             }
426              
427             sub expand {
428 4     4   6 my ($self, $interp) = @_;
429 6   33     25 my @out = map {
430 4         10 my @expand = SQL::Interpolate::Macro::sql_flatten $interp || (), $_;
431 6 100       21 (@expand == 0) ? () : ('OR', '(', @expand, ')')
432             } @$self;
433 4         6 shift @out;
434 4 100       13 return '1=0' if @out == 0; # trivial case
435 2 50       16 @out = ('(', @out, ')') if @out != 0;
436 2         10 return @out;
437             }
438              
439             1;
440              
441             package SQL::Interpolate::If;
442 4     4   19 use strict;
  4         9  
  4         133  
443 4     4   18 use base 'SQL::Interpolate::Macro';
  4         6  
  4         765  
444              
445             sub new {
446 0     0     my ($class, $condition, $value_if_true) = @_;
447              
448 0           my $self = bless [
449             $condition, $value_if_true
450             ], $class;
451 0           return $self;
452             }
453              
454             sub expand {
455 0     0     my ($self, $interp) = @_;
456 0 0 0       return $self->[0]
457             ? SQL::Interpolate::Macro::sql_flatten($interp || (), $self->[1])
458             : ();
459             }
460              
461             1;
462              
463             __END__