File Coverage

blib/lib/SQL/Maker.pm
Criterion Covered Total %
statement 159 200 79.5
branch 73 98 74.4
condition 9 20 45.0
subroutine 24 30 80.0
pod 8 11 72.7
total 273 359 76.0


line stmt bran cond sub pod time code
1             package SQL::Maker;
2 15     15   255008 use strict;
  15         31  
  15         554  
3 15     15   65 use warnings;
  15         20  
  15         357  
4 15     15   434 use 5.008001;
  15         44  
  15         905  
5             our $VERSION = '1.21';
6             use Class::Accessor::Lite 0.05 (
7 15         99 ro => [qw/quote_char name_sep new_line strict driver select_class/],
8 15     15   7682 );
  15         14879  
9              
10 15     15   1518 use Carp ();
  15         22  
  15         217  
11 15     15   6261 use SQL::Maker::Select;
  15         37  
  15         485  
12 15     15   6403 use SQL::Maker::Select::Oracle;
  15         28  
  15         389  
13 15     15   81 use SQL::Maker::Condition;
  15         22  
  15         416  
14 15     15   64 use SQL::Maker::Util;
  15         16  
  15         235  
15 15     15   7904 use Module::Load ();
  15         13603  
  15         295  
16 15     15   258 use Scalar::Util ();
  15         16  
  15         970  
17              
18             sub load_plugin {
19 1     1 0 12 my ($class, $role) = @_;
20 1 50       9 $role = $role =~ s/^\+// ? $role : "SQL::Maker::Plugin::$role";
21 1         5 Module::Load::load($role);
22              
23 15     15   66 no strict 'refs';
  15         19  
  15         28079  
24 1         10 for (@{"${role}::EXPORT"}) {
  1         6  
25 1         2 *{"${class}::$_"} = *{"${role}::$_"};
  1         10  
  1         3  
26             }
27             }
28              
29             sub new {
30 29     29 1 41440 my $class = shift;
31 29 50       144 my %args = @_ == 1 ? %{$_[0]} : @_;
  0         0  
32 29 50       77 unless ($args{driver}) {
33 0         0 Carp::croak("'driver' is required for creating new instance of $class");
34             }
35 29         44 my $driver = $args{driver};
36 29 100       70 unless ( defined $args{quote_char} ) {
37 18         14 $args{quote_char} = do{
38 18 100       45 if ($driver eq 'mysql') {
39 10         22 q{`}
40             } else {
41 8         18 q{"}
42             }
43             };
44             }
45 29 50       70 $args{select_class} = $driver eq 'Oracle' ? 'SQL::Maker::Select::Oracle' : 'SQL::Maker::Select';
46              
47 29         190 return bless {
48             name_sep => '.',
49             new_line => "\n",
50             strict => 0,
51             %args
52             }, $class;
53             }
54              
55             sub new_condition {
56 88     88 1 11331 my $self = shift;
57              
58 88         332 SQL::Maker::Condition->new(
59             quote_char => $self->{quote_char},
60             name_sep => $self->{name_sep},
61             strict => $self->{strict},
62             );
63             }
64              
65             sub new_select {
66 84     84 1 374 my $self = shift;
67 84 50       197 my %args = @_==1 ? %{$_[0]} : @_;
  0         0  
68              
69 84         193 return $self->select_class->new(
70             name_sep => $self->name_sep,
71             quote_char => $self->quote_char,
72             new_line => $self->new_line,
73             strict => $self->strict,
74             %args,
75             );
76             }
77              
78             # $builder->insert($table, \%values, \%opt);
79             # $builder->insert($table, \@values, \%opt);
80             sub insert {
81 3     3 1 11 my ($self, $table, $values, $opt) = @_;
82 3   50     32 my $prefix = $opt->{prefix} || 'INSERT INTO';
83              
84 3         8 my $quoted_table = $self->_quote($table);
85              
86 3         4 my (@columns, @bind_columns, @quoted_columns, @values);
87 3 50       16 @values = ref $values eq 'HASH' ? %$values : @$values;
88 3         39 while (my ($col, $val) = splice(@values, 0, 2)) {
89 2         6 push @quoted_columns, $self->_quote($col);
90 2 50       8 if (Scalar::Util::blessed($val)) {
91 0 0       0 if ($val->can('as_sql')) {
92 0     0   0 push @columns, $val->as_sql(undef, sub { $self->_quote($_[0]) });
  0         0  
93 0         0 push @bind_columns, $val->bind();
94             } else {
95 0         0 push @columns, '?';
96 0         0 push @bind_columns, $val;
97             }
98             } else {
99 2 50 33     5 Carp::croak("cannot pass in an unblessed ref as an argument in strict mode")
100             if ref($val) && $self->strict;
101 2 50 33     10 if (ref($val) eq 'SCALAR') {
    50          
102             # $builder->insert(foo => { created_on => \"NOW()" });
103 0         0 push @columns, $$val;
104             }
105             elsif (ref($val) eq 'REF' && ref($$val) eq 'ARRAY') {
106             # $builder->insert( foo => \[ 'UNIX_TIMESTAMP(?)', '2011-04-12 00:34:12' ] );
107 0         0 my ( $stmt, @sub_bind ) = @{$$val};
  0         0  
108 0         0 push @columns, $stmt;
109 0         0 push @bind_columns, @sub_bind;
110             }
111             else {
112             # normal values
113 2         4 push @columns, '?';
114 2         7 push @bind_columns, $val;
115             }
116             }
117             }
118              
119             # Insert an empty record in SQLite.
120             # ref. https://github.com/tokuhirom/SQL-Maker/issues/11
121 3 100 66     9 if ($self->driver eq 'SQLite' && @columns==0) {
122 1         26 my $sql = "$prefix $quoted_table" . $self->new_line . 'DEFAULT VALUES';
123 1         9 return ($sql);
124             }
125              
126 2         21 my $sql = "$prefix $quoted_table" . $self->new_line;
127 2         16 $sql .= '(' . join(', ', @quoted_columns) .')' . $self->new_line .
128             'VALUES (' . join(', ', @columns) . ')';
129              
130 2         17 return ($sql, @bind_columns);
131             }
132              
133             sub _quote {
134 29     29   33 my ($self, $label) = @_;
135              
136 29         65 SQL::Maker::Util::quote_identifier($label, $self->quote_char(), $self->name_sep());
137             }
138              
139             sub delete {
140 16     16 1 212 my ($self, $table, $where, $opt) = @_;
141              
142 16         25 my $w = $self->_make_where_clause($where);
143 16         31 my $quoted_table = $self->_quote($table);
144 16         31 my $sql = "DELETE FROM $quoted_table";
145 16 100       30 if ($opt->{using}) {
146             # $bulder->delete('foo', \%where, { using => 'bar' });
147             # $bulder->delete('foo', \%where, { using => ['bar', 'qux'] });
148 4 100       11 my $tables = ref($opt->{using}) eq 'ARRAY' ? $opt->{using} : [$opt->{using}];
149 4         8 my $using = join(', ', map { $self->_quote($_) } @$tables);
  6         7  
150 4         8 $sql .= " USING " . $using;
151             }
152 16         26 $sql .= $w->[0];
153 16         15 return ($sql, @{$w->[1]});
  16         53  
154             }
155              
156             sub update {
157 0     0 1 0 my ($self, $table, $args, $where) = @_;
158              
159 0         0 my ($columns, $bind_columns) = $self->make_set_clause($args);
160              
161 0         0 my $w = $self->_make_where_clause($where);
162 0         0 push @$bind_columns, @{$w->[1]};
  0         0  
163              
164 0         0 my $quoted_table = $self->_quote($table);
165 0         0 my $sql = "UPDATE $quoted_table SET " . join(', ', @$columns) . $w->[0];
166 0         0 return ($sql, @$bind_columns);
167             }
168              
169             # make "SET" clause.
170             sub make_set_clause {
171 1     1 0 1 my ($self, $args) = @_;
172              
173 1         2 my (@columns, @bind_columns);
174 1 50       5 my @args = ref $args eq 'HASH' ? %$args : @$args;
175 1         28 while (my ($col, $val) = splice @args, 0, 2) {
176 2         4 my $quoted_col = $self->_quote($col);
177 2 50       5 if (Scalar::Util::blessed($val)) {
178 0 0       0 if ($val->can('as_sql')) {
179 0     0   0 push @columns, "$quoted_col = " . $val->as_sql(undef, sub { $self->_quote($_[0]) });
  0         0  
180 0         0 push @bind_columns, $val->bind();
181             } else {
182 0         0 push @columns, "$quoted_col = ?";
183 0         0 push @bind_columns, $val;
184             }
185             } else {
186 2 50 33     8 Carp::croak("cannot pass in an unblessed ref as an argument in strict mode")
187             if ref($val) && $self->strict;
188 2 50 33     8 if (ref $val eq 'SCALAR') {
    50          
189             # $builder->update(foo => { created_on => \"NOW()" });
190 0         0 push @columns, "$quoted_col = " . $$val;
191             }
192             elsif (ref $val eq 'REF' && ref $$val eq 'ARRAY' ) {
193             # $builder->update( foo => \[ 'VALUES(foo) + ?', 10 ] );
194 0         0 my ( $stmt, @sub_bind ) = @{$$val};
  0         0  
195 0         0 push @columns, "$quoted_col = " . $stmt;
196 0         0 push @bind_columns, @sub_bind;
197             }
198             else {
199             # normal values
200 2         3 push @columns, "$quoted_col = ?";
201 2         10 push @bind_columns, $val;
202             }
203             }
204             }
205 1         3 return (\@columns, \@bind_columns);
206             }
207              
208             sub where {
209 0     0 1 0 my ($self, $where) = @_;
210 0         0 my $cond = $self->_make_where_condition($where);
211 0     0   0 return ($cond->as_sql(undef, sub { $self->_quote($_[0]) }), $cond->bind());
  0         0  
212             }
213              
214             sub _make_where_condition {
215 87     87   105 my ($self, $where) = @_;
216              
217 87 50       145 return $self->new_condition unless $where;
218 87 100 66     303 if ( Scalar::Util::blessed( $where ) and $where->can('as_sql') ) {
219 9         22 return $where;
220             }
221              
222 78         138 my $w = $self->new_condition;
223 78 100       298 my @w = ref $where eq 'ARRAY' ? @$where : %$where;
224 78         596 while (my ($col, $val) = splice @w, 0, 2) {
225 59         128 $w->add($col => $val);
226             }
227 78         225 return $w;
228             }
229              
230             sub _make_where_clause {
231 16     16   17 my ($self, $where) = @_;
232              
233 16 100       39 return ['', []] unless $where;
234              
235 13         24 my $w = $self->_make_where_condition($where);
236 13     0   58 my $sql = $w->as_sql(undef, sub { $self->_quote($_[0]) });
  0         0  
237 13 50       60 return [$sql ? " WHERE $sql" : '', [$w->bind]];
238             }
239              
240             # my($stmt, @bind) = $sql−>select($table, \@fields, \%where, \%opt);
241             sub select {
242 76     76 1 139439 my $stmt = shift->select_query(@_);
243 76         167 return ($stmt->as_sql,@{$stmt->bind});
  73         154  
244             }
245              
246             sub select_query {
247 81     81 0 907 my ($self, $table, $fields, $where, $opt) = @_;
248              
249 81 50       228 unless (ref $fields eq 'ARRAY') {
250 0         0 Carp::croak("SQL::Maker::select_query: \$fields should be ArrayRef[Str]");
251             }
252              
253 81         160 my $stmt = $self->new_select;
254 81         144 for my $field (@$fields) {
255 113 100       350 $stmt->add_select(ref $field eq 'ARRAY' ? @$field : $field);
256             }
257              
258 81 100       198 if ( defined $table ) {
259 78 100       132 unless ( ref $table ) {
260             # $table = 'foo'
261 70         147 $stmt->add_from( $table );
262             }
263             else {
264             # $table = [ 'foo', [ bar => 'b' ] ]
265 8         13 for ( @$table ) {
266 14 100       43 $stmt->add_from( ref $_ eq 'ARRAY' ? @$_ : $_ );
267             }
268             }
269             }
270              
271 81 100       178 $stmt->prefix($opt->{prefix}) if $opt->{prefix};
272              
273 81 100       150 if ( $where ) {
274 74         143 $stmt->set_where($self->_make_where_condition($where));
275             }
276              
277 81 100       157 if ( my $joins = $opt->{joins} ) {
278 3         8 for my $join ( @$joins ) {
279 3 50       14 $stmt->add_join(ref $join eq 'ARRAY' ? @$join : $join);
280             }
281             }
282              
283 81 100       172 if (my $o = $opt->{order_by}) {
284 27 100       75 if (ref $o eq 'ARRAY') {
    100          
285 6         11 for my $order (@$o) {
286 12 100       24 if (ref $order eq 'HASH') {
287             # Skinny-ish [{foo => 'DESC'}, {bar => 'ASC'}]
288 3         11 $stmt->add_order_by(%$order);
289             } else {
290             # just ['foo DESC', 'bar ASC']
291 9         43 $stmt->add_order_by(\$order);
292             }
293             }
294             } elsif (ref $o eq 'HASH') {
295             # Skinny-ish {foo => 'DESC'}
296 3         11 $stmt->add_order_by(%$o);
297             } else {
298             # just 'foo DESC, bar ASC'
299 18         55 $stmt->add_order_by(\$o);
300             }
301             }
302 81 100       148 if (my $o = $opt->{group_by}) {
303 12 100       36 if (ref $o eq 'ARRAY') {
    100          
304 6         8 for my $group (@$o) {
305 12 100       20 if (ref $group eq 'HASH') {
306             # Skinny-ish [{foo => 'DESC'}, {bar => 'ASC'}]
307 3         13 $stmt->add_group_by(%$group);
308             } else {
309             # just ['foo DESC', 'bar ASC']
310 9         15 $stmt->add_group_by(\$group);
311             }
312             }
313             } elsif (ref $o eq 'HASH') {
314             # Skinny-ish {foo => 'DESC'}
315 3         11 $stmt->add_group_by(%$o);
316             } else {
317             # just 'foo DESC, bar ASC'
318 3         10 $stmt->add_group_by(\$o);
319             }
320             }
321 81 100       141 if (my $o = $opt->{index_hint}) {
322 3         8 $stmt->add_index_hint($table, $o);
323             }
324              
325 81 100       191 $stmt->limit( $opt->{limit} ) if defined $opt->{limit};
326 81 100       147 $stmt->offset( $opt->{offset} ) if $opt->{offset};
327              
328 81 50       158 if (my $terms = $opt->{having}) {
329 0         0 while (my ($col, $val) = each %$terms) {
330 0         0 $stmt->add_having($col => $val);
331             }
332             }
333              
334 81 50       125 $stmt->for_update(1) if $opt->{for_update};
335 81         128 return $stmt;
336             }
337              
338             1;
339             __END__