File Coverage

blib/lib/SQL/Concat.pm
Criterion Covered Total %
statement 94 103 91.2
branch 34 44 77.2
condition 10 15 66.6
subroutine 26 27 96.3
pod 14 19 73.6
total 178 208 85.5


line stmt bran cond sub pod time code
1             package SQL::Concat;
2 3     3   248521 use 5.010;
  3         9  
3 3     3   13 use strict;
  3         5  
  3         45  
4 3     3   11 use warnings;
  3         9  
  3         56  
5 3     3   14 use Carp;
  3         8  
  3         205  
6              
7             our $VERSION = "0.002";
8              
9 3         27 use MOP4Import::Base::Configure -as_base
10             , [fields => qw/sql bind/
11             , [sep => default => ' ']]
12 3     3   1339 ;
  3         55016  
13 3     3   2864 use MOP4Import::Util qw/lexpand terse_dump/;
  3         7  
  3         2939  
14              
15             sub SQL {
16 28     28 1 7555 MY->new(sep => ' ')->concat(@_);
17             }
18              
19             sub PAR {
20 1     1 1 3493 SQL(@_)->paren;
21             }
22              
23             # Useful for OPT("limit ?", $limit, OPT("offset ?", $offset))
24             sub OPT {
25 10     10 1 15464 my ($expr, $value, @rest) = @_;
26 10 100       34 return unless defined $value;
27 6         17 SQL([$expr, $value], @rest);
28             }
29              
30             sub PFX {
31 6     6 1 3657 my ($prefix, @items) = @_;
32 6 100       19 return unless @items;
33 5 50       13 my @non_empty = _nonempty(@items)
34             or return;
35 0         0 SQL($prefix => @non_empty);
36             }
37              
38             sub _nonempty {
39             grep {
40 18     18   34 my MY $item = $_;
  35         61  
41 35 100 66     289 if (not defined $item
    100 66        
    100 100        
42             or not ref $item and $item !~ /\S/) {
43 4         12 ();
44             } elsif (ref $item eq 'ARRAY') {
45 6         25 $item;
46             } elsif (ref $item and UNIVERSAL::can($item, 'is_empty')
47             and $item->is_empty) {
48 9         34 ();
49             } else {
50 16         45 $item;
51             }
52             } @_;
53             }
54              
55             # sub SELECT {
56             # MY->new(sep => ' ')->concat(SELECT => @_);
57             # }
58              
59             sub CAT {
60 4     4 1 520 MY->concat_by(_wrap_ws($_[0]), @_[1..$#_]);
61             }
62              
63             sub CSV {
64 1     1 1 670 MY->concat_by(', ', @_);
65             }
66              
67             sub _wrap_ws {
68 4     4   8 my ($str) = @_;
69 4         19 $str =~ s/^(\S)/ $1/;
70 4         14 $str =~ s/(\S)\z/$1 /;
71 4         17 $str;
72             }
73              
74             # XXX: Do you want deep copy?
75             sub clone {
76 5     5 0 12 (my MY $item) = @_;
77 5         20 MY->new(%$item)
78             }
79              
80             sub is_empty {
81 21     21 1 39 (my MY $item) = @_;
82 21         88 $item->{sql} !~ /\S/
83             }
84              
85             sub paren {
86 4     4 1 9 (my MY $item) = @_;
87 4 50       10 if (_nonempty($item)) {
88 4         11 $item->format_by('(%s)')
89             } else {
90 0         0 return;
91             }
92             }
93              
94             sub paren_nl_indent {
95 1     1 1 4 (my MY $item, my $indent) = @_;
96 1 50       3 if (_nonempty($item)) {
97 1   50     12 $item->format_by("(\n%s\n)", $indent || 2)
98             } else {
99 0         0 return;
100             }
101             }
102              
103             sub format_by {
104 5     5 1 13 (my MY $item, my ($fmt, $indent)) = @_;
105 5         13 my MY $clone = $item->clone;
106 5         737 my $sql = $item->{sql};
107 5 100       19 $sql =~ s/^/" " x $indent/emg if $indent;
  1         4  
108 5         22 $clone->{sql} = sprintf($fmt, $sql);
109 5         29 $clone;
110             }
111              
112             sub concat_by {
113 8 50   8 1 2439 my MY $self = ref $_[0]
114             ? shift->configure(sep => shift)
115             : shift->new(sep => shift);
116 8         1025 $self->concat(_nonempty(@_));
117             }
118              
119             #
120             # XXX: Could have more extension hook, hmm...
121             #
122             sub concat {
123 62 100   62 1 29951 my MY $self = ref $_[0] ? shift : shift->new;
124 62 50       3917 if (defined $self->{sql}) {
125 0         0 croak "concat() called after concat!";
126             }
127 62         96 my @sql;
128 62         121 $self->{bind} = [];
129 62         121 foreach my MY $item (@_) {
130 103 100       219 next unless defined $item;
131 102 100       204 if (not ref $item) {
132 57         111 push @sql, $item;
133             } else {
134              
135 45 100       173 $item = $self->of_bind_array($item)
136             if ref $item eq 'ARRAY';
137              
138 45         3468 $item->validate_placeholders;
139              
140 42         73 push @sql, $item->{sql};
141 42         60 push @{$self->{bind}}, @{$item->{bind}};
  42         71  
  42         89  
142             }
143             }
144 59         153 $self->{sql} = join($self->{sep}, @sql);
145 59         237 $self
146             }
147              
148             sub of_bind_array {
149 23     23 0 49 (my MY $self, my $bind_array) = @_;
150 23         54 my ($s, @b) = @$bind_array;
151 23         70 $self->new(sql => $s, bind => \@b);
152             }
153              
154             sub validate_placeholders {
155 45     45 0 74 (my MY $self) = @_;
156              
157 45 50       114 my $nbinds = $self->{bind} ? @{$self->{bind}} : 0;
  45         77  
158              
159 45 100       99 unless ($self->count_placeholders == $nbinds) {
160             croak "SQL Placeholder mismatch! sql='$self->{sql}' bind="
161 2         10 .terse_dump($self->{bind});
162             }
163              
164 42         86 $self;
165             }
166              
167             sub count_placeholders {
168 44     44 0 70 (my MY $self) = @_;
169              
170 44 100       125 unless (defined $self->{sql}) {
171 1         94 croak "Undefined SQL Fragment!";
172             }
173              
174 43         121 $self->{sql} =~ tr,?,?,;
175             }
176              
177             sub as_sql_bind {
178 16     16 1 45138 (my MY $self) = @_;
179 16 100       42 if (wantarray) {
180 15         54 ($self->{sql}, lexpand($self->{bind}));
181             } else {
182 1         7 [$self->{sql}, lexpand($self->{bind})];
183             }
184             }
185              
186             sub sql_bind_pair {
187 2     2 1 7952 (my MY $self) = @_;
188 2 100       9 if (wantarray) {
189 1   50     16 ($self->{sql}, $self->{bind} // [])
190             } else {
191 1   50     12 [$self->{sql}, $self->{bind} // []];
192             }
193             }
194              
195              
196             #========================================
197              
198             sub BQ {
199 0 0   0 0   if (ref $_[0]) {
200 0           croak "Meaningless backtick for reference! ".terse_dump($_[0]);
201             }
202 0 0         if ($_[0] =~ /\`/) {
203 0           croak "Can't quote by backtick: text contains backtick! $_[0]";
204             }
205 0           q{`}.$_[0].q{`}
206             }
207              
208             1;
209              
210              
211             __END__