File Coverage

lib/SQL/Concat.pm
Criterion Covered Total %
statement 119 131 90.8
branch 57 72 79.1
condition 17 30 56.6
subroutine 35 38 92.1
pod 18 28 64.2
total 246 299 82.2


line stmt bran cond sub pod time code
1             package SQL::Concat;
2 5     5   1106404 use 5.010;
  5         23  
3 5     5   24 use strict;
  5         9  
  5         146  
4 5     5   26 use warnings;
  5         7  
  5         280  
5 5     5   25 use Carp;
  5         7  
  5         568  
6              
7             our $VERSION = "0.010";
8              
9 5         66 use MOP4Import::Base::Configure -as_base
10             , [fields => qw/sql bind/
11             , [sep => default => ' ']]
12 5     5   2540 ;
  5         365281  
13 5     5   10188 use MOP4Import::Util qw/lexpand terse_dump/;
  5         10  
  5         371  
14              
15             use overload
16 5         61 '.' => 'operator_concat',
17             'bool' => 'operator_bool',
18             'eq' => 'operator_eq',
19             'ne' => 'operator_ne',
20 5     5   31 ;
  5         9  
21              
22             sub operator_bool {
23 30     30 0 185219 (my MY $self) = @_;
24 30 50       225 defined $self->{sql} and $self->{sql} ne '';
25             }
26              
27             sub operator_concat {
28 8     8 0 26 (my MY $self, my ($other, $swap)) = @_;
29 8 100       33 ref($self)->new(sep => $self->{sep})->concat(
30             _nonempty(
31             $swap ? ($other, $self) : ($self, $other)
32             )
33             );
34             }
35              
36             sub operator_ne {
37 3     3 0 10 (my MY $self, my ($other, $swap)) = @_;
38 3         10 not $self->operator_eq($other, $swap);
39             }
40              
41             sub operator_eq {
42 12     12 0 1383 (my MY $self, my ($other, $swap)) = @_;
43              
44 12   50     36 my $myBind = $self->{bind} // [];
45              
46 12 100       38 if (not defined $other) {
    100          
47 1         8 return;
48             }
49             elsif (not ref $other) {
50 6 50 50     82 @$myBind == 0 && ($self->{sql} // '') eq $other
51             }
52             else {
53 5 100       21 my ($otherSQL, @other) = ref $other eq 'ARRAY'
54             ? @$other : $other->as_sql_bind;
55              
56 5 50 50     78 ($self->{sql} // '') eq ($otherSQL // '')
      50        
57             &&
58             _array_equal($myBind, \@other)
59             }
60             }
61              
62             sub _array_equal {
63 5     5   13 my ($la, $ra) = @_;
64 5 100       23 return unless @$la == @$ra;
65 4         25 for (my $i = 0; $i < @$ra; $i++) {
66 2 100 66     23 if (!defined $la->[$i] and !defined $ra->[$i]) {
    50 25        
    50          
67             # same
68             }
69             elsif (!defined $la->[$i] xor !defined $ra->[$i]) {
70 0         0 return;
71             }
72             elsif ($la->[$i] ne $ra->[$i]) {
73 0         0 return;
74             }
75             }
76 4         55 return 1;
77             }
78              
79             sub TO_JSON {
80 1     1 0 23 (my MY $self) = @_;
81 1         4 [$self->as_sql_bind];
82             }
83              
84             sub SQL {
85 55     55 1 211262 MY->new(sep => ' ')->concat(@_);
86             }
87              
88             sub Q {
89 23 100   23 1 434158 SQL(@_ ? [@_] : ())
90             }
91              
92             *PAR = *PAREN; *PAR = *PAREN;
93             sub PAREN {
94 1     1 1 605 SQL(@_)->paren;
95             }
96              
97             sub WHERE {
98 2     2 1 7 PFX(WHERE => @_);
99             }
100              
101             sub AND {
102 0     0 1 0 CAT(AND => @_)->paren;
103             }
104              
105             sub OR {
106 0     0 1 0 CAT(OR => @_)->paren;
107             }
108              
109             # Useful for OPT("limit ?", $limit, OPT("offset ?", $offset))
110             sub OPT {
111 12     12 1 12773 my ($expr, $value, @rest) = @_;
112 12 50       60 return wantarray ? () : SQL() unless defined $value;
    100          
113 7         24 SQL([$expr, $value], @rest);
114             }
115              
116             sub PFX {
117 8     8 1 3562 my ($prefix, @items) = @_;
118 8 100       39 my @non_empty = @items ? _nonempty(@items) : ()
    100          
    100          
119             or return wantarray ? () : SQL();
120 1         4 SQL($prefix => @non_empty);
121             }
122              
123             sub _nonempty {
124             grep {
125 27     27   1658 my MY $item = $_;
  52         67  
126 52 100 66     472 if (not defined $item
    100 66        
    100 100        
127             or not ref $item and $item !~ /\S/) {
128 4         14 ();
129             } elsif (ref $item eq 'ARRAY') {
130 6         15 $item;
131             } elsif (ref $item and UNIVERSAL::can($item, 'is_empty')
132             and $item->is_empty) {
133 14         55 ();
134             } else {
135 28         99 $item;
136             }
137             } @_;
138             }
139              
140             # sub SELECT {
141             # MY->new(sep => ' ')->concat(SELECT => @_);
142             # }
143              
144             sub CAT {
145 4     4 1 571 MY->concat_by(_wrap_ws($_[0]), @_[1..$#_]);
146             }
147              
148             sub CSV {
149 1     1 1 752 MY->concat_by(', ', @_);
150             }
151              
152             sub _wrap_ws {
153 4     4   8 my ($str) = @_;
154 4         26 $str =~ s/^(\S)/ $1/;
155 4         19 $str =~ s/(\S)\z/$1 /;
156 4         18 $str;
157             }
158              
159             # XXX: Do you want deep copy?
160             sub clone {
161 5     5 0 9 (my MY $item) = @_;
162 5         38 MY->new(%$item)
163             }
164              
165             sub is_empty {
166 32     32 1 57 (my MY $item) = @_;
167 32         131 $item->{sql} !~ /\S/
168             }
169              
170             sub paren {
171 4     4 1 9 (my MY $item) = @_;
172 4 50       11 if (_nonempty($item)) {
173 4         12 $item->format_by('(%s)')
174             } else {
175 0         0 return;
176             }
177             }
178              
179             sub paren_nl_indent {
180 1     1 1 2 (my MY $item, my $indent) = @_;
181 1 50       2 if (_nonempty($item)) {
182 1   50     7 $item->format_by("(\n%s\n)", $indent || 2)
183             } else {
184 0         0 return;
185             }
186             }
187              
188             sub format_by {
189 5     5 1 11 (my MY $item, my ($fmt, $indent)) = @_;
190 5         13 my MY $clone = $item->clone;
191 5         648 my $sql = $item->{sql};
192 5 100       33 $sql =~ s/^/" " x $indent/emg if $indent;
  1         7  
193 5         22 $clone->{sql} = sprintf($fmt, $sql);
194 5         21 $clone;
195             }
196              
197             sub concat_by {
198 8 50   8 1 2293 my MY $self = ref $_[0]
199             ? shift->configure(sep => shift)
200             : shift->new(sep => shift);
201 8         1148 $self->concat(_nonempty(@_));
202             }
203              
204             #
205             # XXX: Could have more extension hook, hmm...
206             #
207             sub concat {
208 97 100   97 1 28133 my MY $self = ref $_[0] ? shift : shift->new;
209 97 50       3604 if (defined $self->{sql}) {
210 0         0 croak "concat() called after concat!";
211             }
212 97         132 my @sql;
213 97         182 $self->{bind} = [];
214 97         159 foreach my MY $item (@_) {
215 131 100       236 next unless defined $item;
216 130 100       223 if (not ref $item) {
217 64         142 push @sql, $item;
218             } else {
219              
220 66 100       224 $item = $self->of_bind_array($item)
221             if ref $item eq 'ARRAY';
222              
223 66         6369 $item->validate_placeholders;
224              
225 63         105 push @sql, $item->{sql};
226 63         129 push @{$self->{bind}}, @{$item->{bind}};
  63         104  
  63         144  
227             }
228             }
229 94         259 $self->{sql} = join($self->{sep}, @sql);
230 94         462 $self
231             }
232              
233             sub of_bind_array {
234 38     38 0 71 (my MY $self, my $bind_array) = @_;
235 38         100 my ($s, @b) = @$bind_array;
236 38         105 $self->new(sql => $s, bind => \@b);
237             }
238              
239             sub validate_placeholders {
240 66     66 0 98 (my MY $self) = @_;
241              
242 66 50       138 my $nbinds = $self->{bind} ? @{$self->{bind}} : 0;
  66         117  
243              
244 66 100       130 unless ($self->count_placeholders == $nbinds) {
245             croak "SQL Placeholder mismatch! sql='$self->{sql}' bind="
246 2         11 .terse_dump($self->{bind});
247             }
248              
249 63         99 $self;
250             }
251              
252             sub count_placeholders {
253 65     65 0 96 (my MY $self) = @_;
254              
255 65 100       122 unless (defined $self->{sql}) {
256 1         186 croak "Undefined SQL Fragment!";
257             }
258              
259 64         195 $self->{sql} =~ tr,?,?,;
260             }
261              
262             sub as_sql_bind {
263 24     24 1 7152 (my MY $self) = @_;
264 24 100       62 if (wantarray) {
265 23         104 ($self->{sql}, lexpand($self->{bind}));
266             } else {
267 1         5 [$self->{sql}, lexpand($self->{bind})];
268             }
269             }
270              
271             sub sql_bind_pair {
272 2     2 1 2000 (my MY $self) = @_;
273 2 100       6 if (wantarray) {
274 1   50     12 ($self->{sql}, $self->{bind} // [])
275             } else {
276 1   50     8 [$self->{sql}, $self->{bind} // []];
277             }
278             }
279              
280              
281             #========================================
282              
283             sub BQ {
284 0 0   0 0   if (ref $_[0]) {
285 0           croak "Meaningless backtick for reference! ".terse_dump($_[0]);
286             }
287 0 0         if ($_[0] =~ /\`/) {
288 0           croak "Can't quote by backtick: text contains backtick! $_[0]";
289             }
290 0           q{`}.$_[0].q{`}
291             }
292              
293             1;
294              
295              
296             __END__