File Coverage

blib/lib/SQL/QueryMaker.pm
Criterion Covered Total %
statement 119 119 100.0
branch 33 50 66.0
condition 14 20 70.0
subroutine 25 25 100.0
pod 4 6 66.6
total 195 220 88.6


line stmt bran cond sub pod time code
1             package SQL::QueryMaker;
2 4     4   22957 use 5.008_001;
  4         13  
  4         156  
3 4     4   21 use strict;
  4         8  
  4         117  
4 4     4   25 use warnings;
  4         11  
  4         126  
5 4     4   4051 use utf8;
  4         37  
  4         20  
6 4     4   121 use Carp ();
  4         6  
  4         101  
7 4     4   18 use Exporter qw(import);
  4         6  
  4         133  
8 4     4   22 use Scalar::Util qw(blessed);
  4         6  
  4         613  
9              
10             our $VERSION = '0.03';
11              
12             our @EXPORT = qw(sql_op sql_raw);
13              
14             {
15 4     4   21 no strict "refs";
  4         35  
  4         7936  
16              
17             for (qw(and or)) {
18             my $fn = "sql_$_";
19             my $op = uc $_;
20              
21             *{__PACKAGE__ . "::$fn"} = sub {
22             # fetch args
23 6     6   8168 my $args = pop;
24 6         15 my $column = shift;
25 6 100       23 if (ref $args eq 'HASH') {
26 1 50       3 Carp::croak("cannot specify the column name as another argument when the conditions are listed using hashref")
27             if defined $column;
28 1         2 my @conds;
29 1         7 for my $column (keys %$args) {
30 3         31 my $value = $args->{$column};
31 3 100 66     58 if (blessed($value) && $value->can('bind_column')) {
32 2         6 $value->bind_column($column);
33             } else {
34 1         3 $value = sql_eq($column, $value);
35             }
36 3         8 push @conds, $value;
37             }
38 1         4 $args = \@conds;
39             } else {
40 5 50       24 Carp::croak("arguments to `$op` must be contained in an arrayref or a hashref")
41             if ref $args ne 'ARRAY';
42             }
43             # build and return the compiler
44             return SQL::QueryMaker->_new($column, sub {
45 5     5   8 my ($column, $quote_cb) = @_;
46 5 0       18 return $op eq 'AND' ? '0=1' : '1=1'
    50          
47             if @$args == 0;
48 5         7 my @term;
49 5         68 for my $arg (@$args) {
50 11 100 66     152 if (blessed($arg) && $arg->can('as_sql')) {
51 9         23 my ($term, $bind) = $arg->as_sql($column, $quote_cb);
52 9         35 push @term, "($term)";
53             } else {
54 2 50       6 Carp::croak("no column binding for $fn")
55             unless defined $column;
56 2         4 push @term, '(' . $quote_cb->($column) . ' = ?)';
57             }
58             }
59 5         15 my $term = join " $op ", @term;
60 5         111 return $term;
61 6         46 }, do {
62 6         519 my @bind;
63 6         15 for my $arg (@$args) {
64 13 100 66     93 if (blessed($arg) && $arg->can('as_sql')) {
65 9         23 push @bind, $arg->bind();
66             } else {
67 4         9 push @bind, $arg;
68             }
69             }
70 6         26 \@bind;
71             });
72             };
73              
74             push @EXPORT, $fn;
75             }
76              
77             for (qw(in not_in)) {
78             my $fn = "sql_$_";
79             my $op = uc $_;
80             $op =~ s/_/ /g;
81              
82             *{__PACKAGE__ . "::$fn"} = sub {
83             # fetch args
84 3     3   4494 my $args = pop;
85 3 50       18 Carp::croak("arguments to `$op` must be contained in an arrayref")
86             if ref $args ne 'ARRAY';
87 3         8 my $column = shift;
88             # build and return the compiler
89             return SQL::QueryMaker->_new($column, sub {
90 2     2   6 my ($column, $quote_cb) = @_;
91 2 50       9 Carp::croak("no column binding for $fn")
92             unless defined $column;
93 2 0       7 return $op eq 'IN' ? '0=1' : '1=1'
    50          
94             if @$args == 0;
95 2         2 my @term;
96 2         7 for my $arg (@$args) {
97 4 100 66     22 if (blessed($arg) && $arg->can('as_sql')) {
98 1         5 my $term = $arg->as_sql(undef, $quote_cb);
99 1 50       10 push @term, $term eq '?' ? $term : "($term)"; # emit parens only when necessary
100             } else {
101 3         10 push @term, '?';
102             }
103             }
104 2         9 my $term = $quote_cb->($column) . " $op (" . join(',', @term) . ')';
105 2         10 return $term;
106 3         21 }, do {
107 3         6 my @bind;
108 3         7 for my $arg (@$args) {
109 6 100 66     40 if (blessed($arg) && $arg->can('as_sql')) {
110 1         5 push @bind, $arg->bind();
111             } else {
112 5         15 push @bind, $arg;
113             }
114             }
115 3         15 \@bind;
116             });
117             };
118              
119             push @EXPORT, $fn;
120             }
121              
122             my %FNOP = (
123             'is_null' => 'IS NULL',
124             'is_not_null' => 'IS NOT NULL',
125             'eq' => '= ?',
126             'ne' => '!= ?',
127             'lt' => '< ?',
128             'gt' => '> ?',
129             'le' => '<= ?',
130             'ge' => '>= ?',
131             'like' => 'LIKE ?',
132             'between' => 'BETWEEN ? AND ?',
133             'not_between' => 'NOT BETWEEN ? AND ?',
134             'not' => 'NOT @',
135             );
136             for (keys %FNOP) {
137             my $fn = "sql_$_";
138             my ($num_args, $builder) = _compile_builder($FNOP{$_});
139              
140             *{__PACKAGE__ . "::$fn"} = sub {
141             # fetch args
142 16 100   16   40840 my $column = @_ > $num_args ? shift : undef;
143 16 50       52 Carp::croak("the operator expects $num_args parameters, but got " . scalar(@_))
144             if $num_args != @_;
145 16         65 return _sql_op($fn, $builder, $column, [ @_ ]);
146             };
147              
148             push @EXPORT, $fn;
149             }
150             }
151              
152             sub sql_op {
153 1     1 1 4690 my $args = pop;
154 1         3 my $expr = pop;
155 1         73 my ($num_args, $builder) = _compile_builder($expr);
156 1 50       6 Carp::croak("the operator expects $num_args but got " . scalar(@$args))
157             if $num_args != @$args;
158 1         5 return _sql_op("sql_op", $builder, shift, $args);
159             }
160              
161             sub _sql_op {
162 17     17   103 my ($fn, $builder, $column, $args) = @_;
163             return SQL::QueryMaker->_new($column, sub {
164 16     16   25 my ($column, $quote_cb) = @_;
165 16 50       33 Carp::croak("no column binding for $fn(args...)")
166             unless defined $column;
167 16         38 my $term = $builder->($quote_cb->($column));
168 16         45 return $term;
169 17         385 }, $args);
170             }
171              
172             sub sql_raw {
173 2     2 1 10527 my ($sql, @bind) = @_;
174             return SQL::QueryMaker->_new(undef, sub {
175 2     2   6 return $sql;
176 2         19 }, \@bind);
177             }
178              
179             sub _compile_builder {
180 49     49   60 my $expr = shift;
181             # substitute the column character
182 49 100       272 $expr = "\@ $expr"
183             if $expr !~ /\@/;
184              
185 49         63 my $num_args = @{[ $expr =~ /\?/g ]};
  49         150  
186 49         177 my @expr = split /\@/, $expr, -1;
187             my $builder = sub {
188 16     16   48 return join $_[0], @expr;
189 49         159 };
190 49         136 return ($num_args, $builder);
191             }
192              
193             sub _new {
194 28     28   117 my ($class, $column, $as_sql, $bind) = @_;
195 28         54 for my $b (@$bind) {
196 34 100 66     610 Carp::croak("cannot bind an arrayref or an hashref")
197             if ref $b && ! blessed($b);
198             }
199 25         409 return bless {
200             column => $column,
201             as_sql => $as_sql,
202             bind => $bind,
203             }, $class;
204             }
205              
206             sub bind_column {
207 4     4 0 7 my ($self, $column) = @_;
208 4 50       11 if (defined $column) {
209 4 50       13 Carp::croak('cannot rebind column for \`' . $self->{column} . "` to: `$column`")
210             if defined $self->{column};
211             }
212 4         9 $self->{column} = $column;
213             }
214              
215             sub as_sql {
216 25     25 1 819 my ($self, $supplied_colname, $quote_cb) = @_;
217 25 100       62 $self->bind_column($supplied_colname)
218             if defined $supplied_colname;
219 25   100     256 $quote_cb ||= \"e_identifier;
220 25         82 return $self->{as_sql}->($self->{column}, $quote_cb);
221             }
222              
223             sub bind {
224 25     25 1 70 my $self = shift;
225 25         30 return @{$self->{bind}};
  25         100  
226             }
227              
228             sub quote_identifier {
229 20     20 0 40 my $label = shift;
230 20         200 return join '.', map { "`$_`" } split /\./, $label;
  20         109  
231             }
232              
233             1;
234             __END__