File Coverage

blib/lib/SQL/Interpolate/Filter.pm
Criterion Covered Total %
statement 44 45 97.7
branch 18 20 90.0
condition n/a
subroutine 6 6 100.0
pod n/a
total 68 71 95.7


line stmt bran cond sub pod time code
1             package SQL::Interpolate::Filter;
2              
3 2     2   625 use strict;
  2         4  
  2         88  
4 2     2   10 use warnings;
  2         4  
  2         50  
5 2     2   2200 use Filter::Simple;
  2         88234  
  2         15  
6 2         2105 use Text::Balanced qw/extract_quotelike
7             extract_bracketed
8             extract_multiple
9             extract_variable
10 2     2   130 extract_codeblock/;
  2         5  
11              
12             our $VERSION = '0.32';
13              
14             # Source filter.
15             # Note: this could be improved as done in the POD of the development 2.0 version of
16             # Text::Balanced.
17             FILTER {
18             my $id = qr/\b(?!([ysm]|q[rqxw]?|tr)\b)\w+/;
19              
20             # This lexes the Perl source code, replacing quotelike sql//
21             # operators with the result of _process_sql().
22             while ($_ !~ /\G$/gc) {
23             my $sql;
24             my $last_pos = pos();
25             if (/\G\s+/gc) { } # whitespace
26             elsif (/\G#.*/gc) { } # comments
27             # sql// operators
28             # FIX:should any other quote delimiters be added?
29             elsif (/\G\bsql\b\s*(?=[\{\(\[\<\/])/gcs &&
30             do {
31             my $pos = pos();
32             s/\G/ q/; # convert to Perl quote-like
33             pos() = $pos;
34             $sql = (extract_quotelike())[5];
35             #print "";
36             if (!$sql) { # restore
37             s/\G q//;
38             pos() = $pos;
39             }
40             !!$sql;
41             }
42             )
43             {
44             my $pos = pos();
45             my $out = _process_sql($sql);
46             pos() = $pos;
47             substr($_, $last_pos, pos() - $last_pos) = $out;
48             pos() = $last_pos + length($out);
49             }
50             # prevent things like $y = ... = from being interpreted as string.
51             elsif (/\G(?<=[\$\@])\w+/gc) {
52             #print "[DEBUG:var:$&]";
53             }
54             elsif (/\G$id/gc) {
55             #print "[DEBUG:id:$&]";
56             }
57             elsif (my $next = (extract_quotelike())[0]) {
58             #print "[DEBUG:q:$next]";
59             }
60             else {
61             /\G./gc;
62             }
63             }
64             print STDERR "DEBUG:filter[code=$_]" if $SQL::Interpolate::trace_filter;
65             };
66              
67             # Convert the string inside a sql// quote-like operator into
68             # a list of SQL strings and variable references for interpolation.
69             sub _process_sql {
70 18     18   38 local $_ = shift;
71              
72 18         19 my @parts;
73 18         22 my $instr = 0;
74 18         48 while ($_ !~ /\G$/gc) {
75 500         1777 my $tok;
76             my $tok_type;
77 500         606 my $pos_last = pos();
78 500 100       3095 if (/\G(\s+|\*)/gc) {
    100          
    100          
79 102         1339 $tok = $1;
80 102         136 $tok_type = 's';
81             }
82             elsif ($tok = (extract_variable($_))[0]) {
83 12         9597 $tok_type = 'v';
84             }
85             elsif ($tok = (extract_codeblock($_, '{['))[0]) {
86 4         2282 $tok_type = 'c';
87             }
88             else {
89 382         66721 /\G(.)/gc;
90 382         648 $tok = $1;
91 382         506 $tok_type = 's';
92             }
93              
94 500 100       1864 if ($tok_type eq 's') {
95 484 100       878 if ($instr) {
96 466         5272 $parts[-1] .= $tok
97             }
98             else {
99 18         31 push @parts, $tok
100             }
101 484         7153 $instr = 1;
102             }
103             else {
104 16 50       67 $parts[-1] = 'qq[' . $parts[-1] . ']' if $instr;
105 16         23 $instr = 0;
106 16 100       43 if ($tok_type eq 'v') {
    50          
107 12         75 push @parts, '\\' . $tok;
108             }
109             elsif ($tok_type eq 'c') {
110 4         18 push @parts, $tok;
111             }
112 0         0 else { die 'assert'; }
113             }
114              
115             }
116 18 100       47 $parts[-1] = 'qq[' . $parts[-1] . ']' if $instr;
117              
118 18         51 my $out = 'SQL::Interpolate::Filter::_make_sql('
119             . join(', ', @parts) . ')';
120              
121 18         72 return $out;
122             }
123              
124             # Generated by the sql// operator when source filtering is enabled.
125             sub _make_sql {
126 18     18   13360 my (@list) = @_;
127              
128             # Note that sql[INSERT INTO mytable $x] gets translated to
129             # q[INSERT INTO mytable], \$x
130             # regardless whether $x is a scalar or reference since it
131             # would be difficult to know at source filtering time whether
132             # $x is already a reference. Therefore, we dereference any
133             # double reference here (at run-time).
134 34 100       116 do { $_ = $$_ if ref($_) eq 'REF' }
135 18         45 for @list;
136              
137 18         87 my $o = SQL::Interpolate::SQL->new(@list);
138 18         89 return $o;
139             }
140              
141             1;
142              
143             # Implementation Notes:
144             # Sub::Quotelike provides similar functionality to this module,
145             # but it is not exactly what I need. Sub::Quotelike allows you to
146             # replace quote expressions with calls to your own custom function
147             # that can return itself and expression. In Sub::Quotelike, the
148             # return expression is evaluated within the context of the called
149             # subroutine rather that in the scope of the caller as is typically
150             # the case with variable interpolation in strings. Therefore, SQL
151             # variable interpolation will not work correctly. Furthermore, the
152             # current version (0.03) performs fairly simple, and potentially
153             # error-prone, source filtering.
154              
155             # We also do not utilize "FILTER_ONLY quotelike" in Filter::Simple
156             # since its parsing is fairly simplistic and recognizes things like $y
157             # = ... = as containing a quote (y=...=).
158              
159             1;
160              
161             __END__