File Coverage

blib/lib/SQL/Concat.pm
Criterion Covered Total %
statement 77 91 84.6
branch 24 34 70.5
condition 2 3 66.6
subroutine 23 25 92.0
pod 11 16 68.7
total 137 169 81.0


line stmt bran cond sub pod time code
1             package SQL::Concat;
2 3     3   383960 use 5.010;
  3         12  
3 3     3   15 use strict;
  3         6  
  3         67  
4 3     3   28 use warnings;
  3         6  
  3         81  
5 3     3   14 use Carp;
  3         5  
  3         293  
6              
7             our $VERSION = "0.001";
8              
9 3         41 use MOP4Import::Base::Configure -as_base
10             , [fields => qw/sql bind/
11             , [sep => default => ' ']]
12 3     3   2330 ;
  3         113292  
13 3     3   2861 use MOP4Import::Util qw/lexpand terse_dump/;
  3         6  
  3         3833  
14              
15             sub SQL {
16 26     26 1 7099 MY->new(sep => ' ')->concat(@_);
17             }
18              
19             sub PAR {
20 1     1 1 4429 SQL(@_)->paren;
21             }
22              
23             # Useful for OPT("limit ?", $limit, OPT("offset ?", $offset))
24             sub OPT {
25 10     10 1 22019 my ($expr, $value, @rest) = @_;
26 10 100       35 return unless defined $value;
27 6         20 SQL([$expr, $value], @rest);
28             }
29              
30             sub PFX {
31 6     6 1 4261 my ($prefix, @items) = @_;
32 6 100       17 return unless @items;
33 5 50       12 my @non_empty = _nonempty(@items)
34             or return;
35 0         0 SQL($prefix => @non_empty);
36             }
37              
38             sub _nonempty {
39             grep {
40 5     5   10 my MY $item = $_;
  12         14  
41 12 100 66     82 if (not defined $item
    50          
42             or not ref $item and $item !~ /\S/) {
43 4         12 ();
44             } elsif ($item->{sql} !~ /\S/) {
45 8         39 ();
46             } else {
47 0         0 $item;
48             }
49             } @_;
50             }
51              
52             # sub SELECT {
53             # MY->new(sep => ' ')->concat(SELECT => @_);
54             # }
55              
56             sub CAT {
57 2     2 1 6 MY->concat_by(_wrap_ws($_[0]), @_[1..$#_]);
58             }
59              
60             sub CSV {
61 1     1 1 713 MY->concat_by(', ', @_);
62             }
63              
64             sub _wrap_ws {
65 2     2   6 my ($str) = @_;
66 2         12 $str =~ s/^(\S)/ $1/;
67 2         9 $str =~ s/(\S)\z/$1 /;
68 2         12 $str;
69             }
70              
71             # XXX: Do you want deep copy?
72             sub clone {
73 4     4 0 9 (my MY $item) = @_;
74 4         45 MY->new(%$item)
75             }
76              
77             sub paren {
78 4     4 1 13 shift->format_by('(%s)');
79             }
80              
81             sub format_by {
82 4     4 1 9 (my MY $item, my $fmt) = @_;
83 4         11 my MY $clone = $item->clone;
84 4         542 $clone->{sql} = sprintf($fmt, $item->{sql});
85 4         17 $clone;
86             }
87              
88             sub concat_by {
89 6 50   6 1 2362 my MY $self = ref $_[0]
90             ? shift->configure(sep => shift)
91             : shift->new(sep => shift);
92 6         749 $self->concat(@_);
93             }
94              
95             sub concat {
96 57 100   57 1 46864 my MY $self = ref $_[0] ? shift : shift->new;
97 57 50       2955 if (defined $self->{sql}) {
98 0         0 croak "concat() called after concat!";
99             }
100 57         76 my @sql;
101 57         112 $self->{bind} = [];
102 57         118 foreach my MY $item (@_) {
103 111 100       248 next unless defined $item;
104 110 100       228 if (not ref $item) {
105 67         115 push @sql, $item;
106             } else {
107              
108 43 100       135 $item = $self->of_bind_array($item)
109             if ref $item eq 'ARRAY';
110              
111 43         2918 $item->validate_placeholders;
112              
113 40         70 push @sql, $item->{sql};
114 40         44 push @{$self->{bind}}, @{$item->{bind}};
  40         86  
  40         104  
115             }
116             }
117 54         147 $self->{sql} = join($self->{sep}, @sql);
118 54         230 $self
119             }
120              
121             sub of_bind_array {
122 22     22 0 36 (my MY $self, my $bind_array) = @_;
123 22         45 my ($s, @b) = @$bind_array;
124 22         71 $self->new(sql => $s, bind => \@b);
125             }
126              
127             sub validate_placeholders {
128 43     43 0 59 (my MY $self) = @_;
129              
130 43 50       99 my $nbinds = $self->{bind} ? @{$self->{bind}} : 0;
  43         78  
131              
132 43 100       98 unless ($self->count_placeholders == $nbinds) {
133             croak "SQL Placeholder mismatch! sql='$self->{sql}' bind="
134 2         13 .terse_dump($self->{bind});
135             }
136              
137 40         60 $self;
138             }
139              
140             sub count_placeholders {
141 43     43 0 60 (my MY $self) = @_;
142              
143 43 100       101 unless (defined $self->{sql}) {
144 1         141 croak "Undefined SQL Fragment!";
145             }
146              
147 42         124 $self->{sql} =~ tr,?,?,;
148             }
149              
150             sub as_sql_bind {
151 13     13 1 2878 (my MY $self) = @_;
152 13 50       30 if (wantarray) {
153 13         46 ($self->{sql}, lexpand($self->{bind}));
154             } else {
155 0           [$self->{sql}, lexpand($self->{bind})];
156             }
157             }
158              
159             #========================================
160              
161             sub BQ {
162 0 0   0 0   if (ref $_[0]) {
163 0           croak "Meaningless backtick for reference! ".terse_dump($_[0]);
164             }
165 0 0         if ($_[0] =~ /\`/) {
166 0           croak "Can't quote by backtick: text contains backtick! $_[0]";
167             }
168 0           q{`}.$_[0].q{`}
169             }
170              
171              
172             sub _sample {
173              
174 0     0     my $name;
175              
176 0           SQL(select => "*" => from => table => );
177              
178 0           my $comp = SQL::Concat->new(sep => ' ')
179             ->concat(SELECT => foo => FROM => 'bar');
180              
181 0           my $composed = SQL(SELECT => "*" =>
182             FROM => entries =>
183             WHERE => ("uid =" =>
184             PAR(SQL(SELECT => uid => FROM => authors =>
185             WHERE => ["name = ?", $name])))
186             );
187              
188 0           my ($sql, @bind) = $composed->as_sql_bind;
189             }
190              
191             1;
192              
193