DBIx-Custom / lib / DBIx / Custom / QueryBuilder.pm /
Newer Older
411 lines | 9.927kb
renamed default_query_filter...
yuki-kimoto authored on 2010-08-03
1
package DBIx::Custom::QueryBuilder;
2

            
3
use strict;
4
use warnings;
5

            
6
use base 'Object::Simple';
7

            
8
use Carp 'croak';
9
use DBIx::Custom::Query;
remove DBIx::Custom::QueryBu...
yuki-kimoto authored on 2010-08-05
10
use DBIx::Custom::QueryBuilder::TagProcessors;
renamed default_query_filter...
yuki-kimoto authored on 2010-08-03
11

            
fixed Carp trast relationshi...
yuki-kimoto authored on 2010-08-12
12
# Carp trust relationship
13
push @DBIx::Custom::CARP_NOT, __PACKAGE__;
14

            
renamed default_query_filter...
yuki-kimoto authored on 2010-08-03
15
__PACKAGE__->dual_attr('tag_processors', default => sub { {} }, inherit => 'hash_copy');
16
__PACKAGE__->register_tag_processor(
renamed update tag to update...
yuki-kimoto authored on 2010-08-09
17
    '?'     => \&DBIx::Custom::QueryBuilder::TagProcessors::expand_placeholder_tag,
18
    '='     => \&DBIx::Custom::QueryBuilder::TagProcessors::expand_equal_tag,
19
    '<>'    => \&DBIx::Custom::QueryBuilder::TagProcessors::expand_not_equal_tag,
20
    '>'     => \&DBIx::Custom::QueryBuilder::TagProcessors::expand_greater_than_tag,
21
    '<'     => \&DBIx::Custom::QueryBuilder::TagProcessors::expand_lower_than_tag,
22
    '>='    => \&DBIx::Custom::QueryBuilder::TagProcessors::expand_greater_than_equal_tag,
23
    '<='    => \&DBIx::Custom::QueryBuilder::TagProcessors::expand_lower_than_equal_tag,
24
    'like'  => \&DBIx::Custom::QueryBuilder::TagProcessors::expand_like_tag,
25
    'in'    => \&DBIx::Custom::QueryBuilder::TagProcessors::expand_in_tag,
26
    'insert_param' => \&DBIx::Custom::QueryBuilder::TagProcessors::expand_insert_param_tag,
27
    'update_param' => \&DBIx::Custom::QueryBuilder::TagProcessors::expand_update_param_tag
renamed default_query_filter...
yuki-kimoto authored on 2010-08-03
28
);
29

            
30
sub register_tag_processor {
31
    my $self = shift;
changed argument of tag proc...
yuki-kimoto authored on 2010-08-03
32
    
remove DBIx::Custom::QueryBu...
yuki-kimoto authored on 2010-08-05
33
    # Merge tag processor
renamed default_query_filter...
yuki-kimoto authored on 2010-08-03
34
    my $tag_processors = ref $_[0] eq 'HASH' ? $_[0] : {@_};
35
    $self->tag_processors({%{$self->tag_processors}, %{$tag_processors}});
changed argument of tag proc...
yuki-kimoto authored on 2010-08-03
36
    
renamed default_query_filter...
yuki-kimoto authored on 2010-08-03
37
    return $self;
38
}
39

            
40
sub build_query {
41
    my ($self, $source)  = @_;
42
    
43
    # Parse
44
    my $tree = $self->_parse($source);
45
    
46
    # Build query
47
    my $query = $self->_build_query($tree);
48
    
49
    return $query;
50
}
51

            
52
sub _parse {
53
    my ($self, $source) = @_;
54
    
remove DBIx::Custom::QueryBu...
yuki-kimoto authored on 2010-08-05
55
    # Source
renamed default_query_filter...
yuki-kimoto authored on 2010-08-03
56
    $source ||= '';
fixed tests
yuki-kimoto authored on 2010-08-06
57

            
remove DBIx::Custom::QueryBu...
yuki-kimoto authored on 2010-08-05
58
    # Tree
removed DBIx::Custom::Query ...
yuki-kimoto authored on 2010-08-12
59
    my @tree;
remove DBIx::Custom::QueryBu...
yuki-kimoto authored on 2010-08-05
60
    
removed DBIx::Custom::Query ...
yuki-kimoto authored on 2010-08-12
61
    # Value
62
    my $value = '';
renamed default_query_filter...
yuki-kimoto authored on 2010-08-03
63
    
removed DBIx::Custom::Query ...
yuki-kimoto authored on 2010-08-12
64
    # State
renamed default_query_filter...
yuki-kimoto authored on 2010-08-03
65
    my $state = 'text';
66
    
removed DBIx::Custom::Query ...
yuki-kimoto authored on 2010-08-12
67
    # Before charactor
68
    my $before = '';
69

            
70
    # Position
71
    my $pos;
renamed default_query_filter...
yuki-kimoto authored on 2010-08-03
72
    
73
    # Parse
added tests
yuki-kimoto authored on 2010-08-12
74
    my $original = $source;
removed DBIx::Custom::Query ...
yuki-kimoto authored on 2010-08-12
75
    while (my $c = substr($source, 0, 1, '')) {
renamed default_query_filter...
yuki-kimoto authored on 2010-08-03
76
        
removed DBIx::Custom::Query ...
yuki-kimoto authored on 2010-08-12
77
        # State is text
78
        if ($state eq 'text') {
79
            
80
            # Tag start charactor
81
            if ($c eq '{') {
82
                
83
                # Escaped charactor
84
                if ($before eq "\\") {
85
                    substr($value, -1, 1, '');
86
                    $value .= $c;
87
                }
88
                
89
                # Tag start
90
                else {
91
                    
92
                    # Change state
93
                    $state = 'tag';
94
                    
95
                    # Add text
96
                    push @tree, {type => 'text', value => $value}
97
                      if $value;
98
                    
99
                    # Clear
100
                    $value = '';
101
                }
102
            }
103
            
104
            # Tag end charactor
105
            elsif ($c eq '}') {
106
            
107
                # Escaped charactor
108
                if ($before eq "\\") {
109
                    substr($value, -1, 1, '');
110
                    $value .= $c;
111
                }
112
                
113
                # Unexpected
114
                else {
115
                    croak qq/Parsing error. unexpected "}". / .
added tests
yuki-kimoto authored on 2010-08-12
116
                          qq/pos $pos of "$original"/;
removed DBIx::Custom::Query ...
yuki-kimoto authored on 2010-08-12
117
                }
118
            }
119
            
120
            # Normal charactor
121
            else { $value .= $c }
122
        }
renamed default_query_filter...
yuki-kimoto authored on 2010-08-03
123
        
removed DBIx::Custom::Query ...
yuki-kimoto authored on 2010-08-12
124
        # State is tags
added tests
yuki-kimoto authored on 2010-08-12
125
        else {
removed DBIx::Custom::Query ...
yuki-kimoto authored on 2010-08-12
126
            
127
            # Tag start charactor
128
            if ($c eq '{') {
renamed default_query_filter...
yuki-kimoto authored on 2010-08-03
129
            
removed DBIx::Custom::Query ...
yuki-kimoto authored on 2010-08-12
130
                # Escaped charactor
131
                if ($before eq "\\") {
132
                    substr($value, -1, 1, '');
133
                    $value .= $c;
134
                }
135
                
136
                # Unexpected
137
                else {
138
                    croak qq/Parsing error. unexpected "{". / .
added tests
yuki-kimoto authored on 2010-08-12
139
                          qq/pos $pos of "$original"/;
removed DBIx::Custom::Query ...
yuki-kimoto authored on 2010-08-12
140
                }
141
            }
renamed default_query_filter...
yuki-kimoto authored on 2010-08-03
142
            
removed DBIx::Custom::Query ...
yuki-kimoto authored on 2010-08-12
143
            # Tag end charactor
144
            elsif ($c eq '}') {
145
                
146
                # Escaped charactor
147
                if ($before eq "\\") {
148
                    substr($value, -1, 1, '');
149
                    $value .= $c;
150
                }
151
                
152
                # Tag end
153
                else {
154
                
155
                    # Change state
156
                    $state = 'text';
157
                    
158
                    # Add tag
159
                    my ($tag_name, @tag_args) = split /\s+/, $value;
160
                    push @tree, {type => 'tag', tag_name => $tag_name, 
161
                                 tag_args => \@tag_args};
162
                    
163
                    # Clear
164
                    $value = '';
165
                }
166
            }
167
            
168
            # Normal charactor
169
            else { $value .= $c }
renamed default_query_filter...
yuki-kimoto authored on 2010-08-03
170
        }
removed DBIx::Custom::Query ...
yuki-kimoto authored on 2010-08-12
171
        
172
        # Save before charactor
173
        $before = $c;
174
        
175
        # increment position
176
        $pos++;
renamed default_query_filter...
yuki-kimoto authored on 2010-08-03
177
    }
178
    
removed DBIx::Custom::Query ...
yuki-kimoto authored on 2010-08-12
179
    # Tag not finished
added tests
yuki-kimoto authored on 2010-08-12
180
    croak qq{Tag not finished. "$original"}
removed DBIx::Custom::Query ...
yuki-kimoto authored on 2010-08-12
181
      if $state eq 'tag';
renamed default_query_filter...
yuki-kimoto authored on 2010-08-03
182
    
removed DBIx::Custom::Query ...
yuki-kimoto authored on 2010-08-12
183
    # Add rest text
184
    push @tree, {type => 'text', value => $value}
185
      if $value;
186
    
187
    return \@tree;
renamed default_query_filter...
yuki-kimoto authored on 2010-08-03
188
}
189

            
190
sub _build_query {
191
    my ($self, $tree) = @_;
192
    
193
    # SQL
194
    my $sql = '';
195
    
changed argument of tag proc...
yuki-kimoto authored on 2010-08-03
196
    # All Columns
renamed default_query_filter...
yuki-kimoto authored on 2010-08-03
197
    my $all_columns = [];
198
    
199
    # Build SQL 
200
    foreach my $node (@$tree) {
201
        
202
        # Text
added tests
yuki-kimoto authored on 2010-08-12
203
        if ($node->{type} eq 'text') { $sql .= $node->{value} }
renamed default_query_filter...
yuki-kimoto authored on 2010-08-03
204
        
205
        # Tag
added tests
yuki-kimoto authored on 2010-08-12
206
        else {
renamed default_query_filter...
yuki-kimoto authored on 2010-08-03
207
            
removed DBIx::Custom::Query ...
yuki-kimoto authored on 2010-08-12
208
            # Tag name
209
            my $tag_name = $node->{tag_name};
210
            
211
            # Tag arguments
212
            my $tag_args = $node->{tag_args};
213
            
renamed default_query_filter...
yuki-kimoto authored on 2010-08-03
214
            # Get tag processor
215
            my $tag_processor = $self->tag_processors->{$tag_name};
216
            
removed DBIx::Custom::Query ...
yuki-kimoto authored on 2010-08-12
217
            # Tag processor is not registered
218
            croak qq{Tag "$tag_name" in "{a }" is not registered}
219
              unless $tag_processor;
220
            
remove DBIx::Custom::QueryBu...
yuki-kimoto authored on 2010-08-05
221
            # Tag processor not sub reference
222
            croak qq{Tag processor "$tag_name" must be sub reference}
renamed default_query_filter...
yuki-kimoto authored on 2010-08-03
223
              unless ref $tag_processor eq 'CODE';
224
            
remove DBIx::Custom::QueryBu...
yuki-kimoto authored on 2010-08-05
225
            # Execute tag processor
fixed Carp trast relationshi...
yuki-kimoto authored on 2010-08-12
226
            my $r = $tag_processor->(@$tag_args);
renamed default_query_filter...
yuki-kimoto authored on 2010-08-03
227
            
228
            # Check tag processor return value
remove DBIx::Custom::QueryBu...
yuki-kimoto authored on 2010-08-05
229
            croak qq{Tag processor "$tag_name" must return [STRING, ARRAY_REFERENCE]}
fixed tests
yuki-kimoto authored on 2010-08-06
230
              unless ref $r eq 'ARRAY' && defined $r->[0] && ref $r->[1] eq 'ARRAY';
renamed default_query_filter...
yuki-kimoto authored on 2010-08-03
231
            
fixed tests
yuki-kimoto authored on 2010-08-06
232
            # Part of SQL statement and colum names
233
            my ($part, $columns) = @$r;
renamed default_query_filter...
yuki-kimoto authored on 2010-08-03
234
            
changed argument of tag proc...
yuki-kimoto authored on 2010-08-03
235
            # Add columns
renamed default_query_filter...
yuki-kimoto authored on 2010-08-03
236
            push @$all_columns, @$columns;
237
            
remove DBIx::Custom::QueryBu...
yuki-kimoto authored on 2010-08-05
238
            # Join part tag to SQL
239
            $sql .= $part;
renamed default_query_filter...
yuki-kimoto authored on 2010-08-03
240
        }
241
    }
fixed tests
yuki-kimoto authored on 2010-08-06
242

            
243
    # Check placeholder count
244
    my $placeholder_count = $self->_placeholder_count($sql);
245
    my $column_count      = @$all_columns;
246
    croak qq{Placeholder count in "$sql" must be same as column count $column_count}
247
      unless $placeholder_count eq @$all_columns;
renamed default_query_filter...
yuki-kimoto authored on 2010-08-03
248
    
249
    # Add semicolon
250
    $sql .= ';' unless $sql =~ /;$/;
251
    
252
    # Query
253
    my $query = DBIx::Custom::Query->new(sql => $sql, columns => $all_columns);
254
    
255
    return $query;
256
}
257

            
258
sub _placeholder_count {
259
    my ($self, $expand) = @_;
260
    
changed argument of tag proc...
yuki-kimoto authored on 2010-08-03
261
    # Count
262
    $expand ||= '';
renamed default_query_filter...
yuki-kimoto authored on 2010-08-03
263
    my $count = 0;
264
    my $pos   = -1;
265
    while (($pos = index($expand, '?', $pos + 1)) != -1) {
266
        $count++;
267
    }
268
    return $count;
269
}
270

            
271
1;
272

            
273
=head1 NAME
274

            
275
DBIx::Custom::QueryBuilder - Query builder
276

            
277
=head1 SYNOPSIS
278
    
279
    my $builder = DBIx::Custom::QueryBuilder->new;
remove DBIx::Custom::QueryBu...
yuki-kimoto authored on 2010-08-05
280
    my $query = $builder->build_query(
281
        "select from table {= k1} && {<> k2} || {like k3}"
282
    );
renamed default_query_filter...
yuki-kimoto authored on 2010-08-03
283

            
284
=head1 ATTRIBUTES
285

            
286
=head2 C<tag_processors>
287

            
288
    my $tag_processors = $builder->tag_processors;
289
    $builder           = $builder->tag_processors(\%tag_processors);
290

            
291
Tag processors.
292

            
293
=head1 METHODS
294

            
remove DBIx::Custom::QueryBu...
yuki-kimoto authored on 2010-08-05
295
L<DBIx::Custom::QueryBuilder> inherits all methods from L<Object::Simple>
296
and implements the following new ones.
renamed default_query_filter...
yuki-kimoto authored on 2010-08-03
297

            
298
=head2 C<build_query>
299
    
300
    my $query = $builder->build_query($source);
301

            
remove DBIx::Custom::QueryBu...
yuki-kimoto authored on 2010-08-05
302
Create a new L<DBIx::Custom::Query> object from SQL source.
303
SQL source contains tags, such as {= title}, {like author}.
renamed default_query_filter...
yuki-kimoto authored on 2010-08-03
304

            
removed DBIx::Custom::Query ...
yuki-kimoto authored on 2010-08-12
305
C<{> and C<}> is reserved. If you use these charactors,
306
you must escape them using '\'. Note that '\' is
307
already perl escaped charactor, so you must write '\\'. 
308

            
309
    'select * from books \\{ something statement \\}'
310

            
renamed default_query_filter...
yuki-kimoto authored on 2010-08-03
311
B<Example:>
312

            
remove DBIx::Custom::QueryBu...
yuki-kimoto authored on 2010-08-05
313
SQL source
renamed default_query_filter...
yuki-kimoto authored on 2010-08-03
314

            
remove DBIx::Custom::QueryBu...
yuki-kimoto authored on 2010-08-05
315
      "select * from table where {= title} && {like author} || {<= price}"
renamed default_query_filter...
yuki-kimoto authored on 2010-08-03
316

            
remove DBIx::Custom::QueryBu...
yuki-kimoto authored on 2010-08-05
317
Query
renamed default_query_filter...
yuki-kimoto authored on 2010-08-03
318

            
remove DBIx::Custom::QueryBu...
yuki-kimoto authored on 2010-08-05
319
    {
320
        sql     => "select * from table where title = ? && author like ? price <= ?;"
321
        columns => ['title', 'author', 'price']
322
    }
renamed default_query_filter...
yuki-kimoto authored on 2010-08-03
323

            
324
=head2 C<register_tag_processor>
325

            
remove DBIx::Custom::QueryBu...
yuki-kimoto authored on 2010-08-05
326
    $builder->register_tag_processor(\%tag_processors);
327
    $builder->register_tag_processor(%tag_processors);
renamed default_query_filter...
yuki-kimoto authored on 2010-08-03
328

            
329
Register tag processor.
330

            
renamed build_query to creat...
yuki-kimoto authored on 2010-08-06
331
B<Example:>
remove DBIx::Custom::QueryBu...
yuki-kimoto authored on 2010-08-05
332

            
renamed default_query_filter...
yuki-kimoto authored on 2010-08-03
333
    $builder->register_tag_processor(
334
        '?' => sub {
remove DBIx::Custom::QueryBu...
yuki-kimoto authored on 2010-08-05
335
            my $column = shift;
renamed default_query_filter...
yuki-kimoto authored on 2010-08-03
336
            
remove DBIx::Custom::QueryBu...
yuki-kimoto authored on 2010-08-05
337
            return ['?', [$column]];
renamed default_query_filter...
yuki-kimoto authored on 2010-08-03
338
        }
339
    );
340

            
renamed build_query to creat...
yuki-kimoto authored on 2010-08-06
341
See also L<DBIx::Custom::QueryBuilder::TagProcessors> to know tag processor.
renamed default_query_filter...
yuki-kimoto authored on 2010-08-03
342

            
343
=head1 Tags
344

            
renamed build_query to creat...
yuki-kimoto authored on 2010-08-06
345
The following tags is available.
update document
yuki-kimoto authored on 2010-08-07
346

            
347
=head2 C<?>
348

            
349
Placeholder tag.
350

            
renamed default_query_filter...
yuki-kimoto authored on 2010-08-03
351
    {? NAME}    ->   ?
update document
yuki-kimoto authored on 2010-08-07
352

            
353
=head2 C<=>
354

            
355
Equal tag.
356

            
renamed default_query_filter...
yuki-kimoto authored on 2010-08-03
357
    {= NAME}    ->   NAME = ?
update document
yuki-kimoto authored on 2010-08-07
358

            
359
=head2 C<E<lt>E<gt>>
360

            
361
Not equal tag.
362

            
renamed default_query_filter...
yuki-kimoto authored on 2010-08-03
363
    {<> NAME}   ->   NAME <> ?
update document
yuki-kimoto authored on 2010-08-07
364

            
365
=head2 C<E<lt>>
366

            
367
Lower than tag
368

            
renamed default_query_filter...
yuki-kimoto authored on 2010-08-03
369
    {< NAME}    ->   NAME < ?
update document
yuki-kimoto authored on 2010-08-07
370

            
371
=head2 C<E<gt>>
372

            
373
Greater than tag
374

            
renamed default_query_filter...
yuki-kimoto authored on 2010-08-03
375
    {> NAME}    ->   NAME > ?
update document
yuki-kimoto authored on 2010-08-07
376

            
377
=head2 C<E<gt>=>
378

            
379
Greater than or equal tag
380

            
renamed default_query_filter...
yuki-kimoto authored on 2010-08-03
381
    {>= NAME}   ->   NAME >= ?
update document
yuki-kimoto authored on 2010-08-07
382

            
383
=head2 C<E<lt>=>
384

            
385
Lower than or equal tag
386

            
renamed default_query_filter...
yuki-kimoto authored on 2010-08-03
387
    {<= NAME}   ->   NAME <= ?
update document
yuki-kimoto authored on 2010-08-07
388

            
389
=head2 C<like>
390

            
391
Like tag
392

            
393
    {like NAME}   ->   NAME like ?
394

            
395
=head2 C<in>
396

            
397
In tag.
398

            
renamed default_query_filter...
yuki-kimoto authored on 2010-08-03
399
    {in NAME COUNT}   ->   NAME in [?, ?, ..]
update document
yuki-kimoto authored on 2010-08-07
400

            
updated document
yuki-kimoto authored on 2010-08-09
401
=head2 C<insert_param>
update document
yuki-kimoto authored on 2010-08-07
402

            
renamed update tag to update...
yuki-kimoto authored on 2010-08-09
403
Insert parameter tag.
update document
yuki-kimoto authored on 2010-08-07
404

            
renamed update tag to update...
yuki-kimoto authored on 2010-08-09
405
    {insert_param NAME1 NAME2}   ->   (NAME1, NAME2) values (?, ?)
update document
yuki-kimoto authored on 2010-08-07
406

            
updated document
yuki-kimoto authored on 2010-08-09
407
=head2 C<update_param>
update document
yuki-kimoto authored on 2010-08-07
408

            
renamed update tag to update...
yuki-kimoto authored on 2010-08-09
409
Updata parameter tag.
update document
yuki-kimoto authored on 2010-08-07
410

            
renamed update tag to update...
yuki-kimoto authored on 2010-08-09
411
    {update_param NAME1 NAME2}   ->   set NAME1 = ?, NAME2 = ?