DBIx-Custom / lib / DBIx / Custom / QueryBuilder.pm /
Newer Older
431 lines | 9.85kb
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;
renamed DBIx::Custom::TagPro...
Yuki Kimoto authored on 2011-01-24
10
use DBIx::Custom::Tag;
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__;
updated document
Yuki Kimoto authored on 2011-01-20
14
push @DBIx::Custom::Where::CARP_NOT, __PACKAGE__;
15

            
cleanup
yuki-kimoto authored on 2010-10-17
16
# Attributes
renamed DBIx::Custom::TagPro...
Yuki Kimoto authored on 2011-01-24
17
__PACKAGE__->attr('tags' => sub {
cleanup (removed undocumente...
yuki-kimoto authored on 2010-11-10
18
    {
renamed DBIx::Custom::TagPro...
Yuki Kimoto authored on 2011-01-24
19
        '?'     => \&DBIx::Custom::Tag::placeholder,
20
        '='     => \&DBIx::Custom::Tag::equal,
21
        '<>'    => \&DBIx::Custom::Tag::not_equal,
22
        '>'     => \&DBIx::Custom::Tag::greater_than,
23
        '<'     => \&DBIx::Custom::Tag::lower_than,
24
        '>='    => \&DBIx::Custom::Tag::greater_than_equal,
25
        '<='    => \&DBIx::Custom::Tag::lower_than_equal,
26
        'like'  => \&DBIx::Custom::Tag::like,
27
        'in'    => \&DBIx::Custom::Tag::in,
28
        'insert_param' => \&DBIx::Custom::Tag::insert_param,
29
        'update_param' => \&DBIx::Custom::Tag::update_param
cleanup (removed undocumente...
yuki-kimoto authored on 2010-11-10
30
    }
31
});
renamed default_query_filter...
yuki-kimoto authored on 2010-08-03
32

            
cleanup
yuki-kimoto authored on 2010-10-17
33
sub build_query {
34
    my ($self, $source)  = @_;
35
    
36
    # Parse
37
    my $tree = $self->_parse($source);
38
    
39
    # Build query
40
    my $query = $self->_build_query($tree);
41
    
42
    return $query;
43
}
44

            
renamed DBIx::Custom::TagPro...
Yuki Kimoto authored on 2011-01-24
45
sub register_tag {
renamed default_query_filter...
yuki-kimoto authored on 2010-08-03
46
    my $self = shift;
changed argument of tag proc...
yuki-kimoto authored on 2010-08-03
47
    
remove DBIx::Custom::QueryBu...
yuki-kimoto authored on 2010-08-05
48
    # Merge tag processor
renamed DBIx::Custom::TagPro...
Yuki Kimoto authored on 2011-01-24
49
    my $tags = ref $_[0] eq 'HASH' ? $_[0] : {@_};
50
    $self->tags({%{$self->tags}, %$tags});
changed argument of tag proc...
yuki-kimoto authored on 2010-08-03
51
    
renamed default_query_filter...
yuki-kimoto authored on 2010-08-03
52
    return $self;
53
}
54

            
cleanup
yuki-kimoto authored on 2010-10-17
55
sub _build_query {
56
    my ($self, $tree) = @_;
renamed default_query_filter...
yuki-kimoto authored on 2010-08-03
57
    
cleanup
yuki-kimoto authored on 2010-10-17
58
    # SQL
59
    my $sql = '';
renamed default_query_filter...
yuki-kimoto authored on 2010-08-03
60
    
cleanup
yuki-kimoto authored on 2010-10-17
61
    # All Columns
62
    my $all_columns = [];
63
    
64
    # Build SQL 
65
    foreach my $node (@$tree) {
66
        
67
        # Text
68
        if ($node->{type} eq 'text') { $sql .= $node->{value} }
69
        
70
        # Tag
71
        else {
72
            
73
            # Tag name
74
            my $tag_name = $node->{tag_name};
75
            
76
            # Tag arguments
77
            my $tag_args = $node->{tag_args};
78
            
79
            # Get tag processor
renamed DBIx::Custom::TagPro...
Yuki Kimoto authored on 2011-01-24
80
            my $tag_processor = $self->tag_processors->{$tag_name}
81
                             || $self->tags->{$tag_name};
cleanup
yuki-kimoto authored on 2010-10-17
82
            
83
            # Tag processor is not registered
84
            croak qq{Tag "$tag_name" in "{a }" is not registered}
85
              unless $tag_processor;
86
            
87
            # Tag processor not sub reference
88
            croak qq{Tag processor "$tag_name" must be sub reference}
89
              unless ref $tag_processor eq 'CODE';
90
            
91
            # Execute tag processor
92
            my $r = $tag_processor->(@$tag_args);
93
            
94
            # Check tag processor return value
95
            croak qq{Tag processor "$tag_name" must return [STRING, ARRAY_REFERENCE]}
96
              unless ref $r eq 'ARRAY' && defined $r->[0] && ref $r->[1] eq 'ARRAY';
97
            
98
            # Part of SQL statement and colum names
99
            my ($part, $columns) = @$r;
100
            
101
            # Add columns
102
            push @$all_columns, @$columns;
103
            
104
            # Join part tag to SQL
105
            $sql .= $part;
106
        }
107
    }
108

            
109
    # Check placeholder count
110
    my $placeholder_count = $self->_placeholder_count($sql);
111
    my $column_count      = @$all_columns;
112
    croak qq{Placeholder count in "$sql" must be same as column count $column_count}
113
      unless $placeholder_count eq @$all_columns;
114
    
115
    # Add semicolon
116
    $sql .= ';' unless $sql =~ /;$/;
117
    
118
    # Query
119
    my $query = DBIx::Custom::Query->new(sql => $sql, columns => $all_columns);
renamed default_query_filter...
yuki-kimoto authored on 2010-08-03
120
    
121
    return $query;
122
}
123

            
124
sub _parse {
125
    my ($self, $source) = @_;
126
    
remove DBIx::Custom::QueryBu...
yuki-kimoto authored on 2010-08-05
127
    # Source
renamed default_query_filter...
yuki-kimoto authored on 2010-08-03
128
    $source ||= '';
fixed tests
yuki-kimoto authored on 2010-08-06
129

            
remove DBIx::Custom::QueryBu...
yuki-kimoto authored on 2010-08-05
130
    # Tree
removed DBIx::Custom::Query ...
yuki-kimoto authored on 2010-08-12
131
    my @tree;
remove DBIx::Custom::QueryBu...
yuki-kimoto authored on 2010-08-05
132
    
removed DBIx::Custom::Query ...
yuki-kimoto authored on 2010-08-12
133
    # Value
134
    my $value = '';
renamed default_query_filter...
yuki-kimoto authored on 2010-08-03
135
    
removed DBIx::Custom::Query ...
yuki-kimoto authored on 2010-08-12
136
    # State
renamed default_query_filter...
yuki-kimoto authored on 2010-08-03
137
    my $state = 'text';
138
    
removed DBIx::Custom::Query ...
yuki-kimoto authored on 2010-08-12
139
    # Before charactor
140
    my $before = '';
141

            
142
    # Position
fixed DBIx::Custom::QueryBui...
yuki-kimoto authored on 2010-08-15
143
    my $pos = 0;
renamed default_query_filter...
yuki-kimoto authored on 2010-08-03
144
    
145
    # Parse
added tests
yuki-kimoto authored on 2010-08-12
146
    my $original = $source;
fixed DBIx::Custom::QueryBui...
yuki-kimoto authored on 2010-08-15
147
    while (defined(my $c = substr($source, $pos, 1))) {
148
        
149
        # Last
150
        last unless length $c;
renamed default_query_filter...
yuki-kimoto authored on 2010-08-03
151
        
removed DBIx::Custom::Query ...
yuki-kimoto authored on 2010-08-12
152
        # State is text
153
        if ($state eq 'text') {
154
            
155
            # Tag start charactor
156
            if ($c eq '{') {
157
                
158
                # Escaped charactor
159
                if ($before eq "\\") {
160
                    substr($value, -1, 1, '');
161
                    $value .= $c;
162
                }
163
                
164
                # Tag start
165
                else {
166
                    
167
                    # Change state
168
                    $state = 'tag';
169
                    
170
                    # Add text
171
                    push @tree, {type => 'text', value => $value}
172
                      if $value;
173
                    
174
                    # Clear
175
                    $value = '';
176
                }
177
            }
178
            
179
            # Tag end charactor
180
            elsif ($c eq '}') {
181
            
182
                # Escaped charactor
183
                if ($before eq "\\") {
184
                    substr($value, -1, 1, '');
185
                    $value .= $c;
186
                }
187
                
188
                # Unexpected
189
                else {
190
                    croak qq/Parsing error. unexpected "}". / .
added tests
yuki-kimoto authored on 2010-08-12
191
                          qq/pos $pos of "$original"/;
removed DBIx::Custom::Query ...
yuki-kimoto authored on 2010-08-12
192
                }
193
            }
194
            
195
            # Normal charactor
196
            else { $value .= $c }
197
        }
renamed default_query_filter...
yuki-kimoto authored on 2010-08-03
198
        
removed DBIx::Custom::Query ...
yuki-kimoto authored on 2010-08-12
199
        # State is tags
added tests
yuki-kimoto authored on 2010-08-12
200
        else {
removed DBIx::Custom::Query ...
yuki-kimoto authored on 2010-08-12
201
            
202
            # Tag start charactor
203
            if ($c eq '{') {
renamed default_query_filter...
yuki-kimoto authored on 2010-08-03
204
            
removed DBIx::Custom::Query ...
yuki-kimoto authored on 2010-08-12
205
                # Escaped charactor
206
                if ($before eq "\\") {
207
                    substr($value, -1, 1, '');
208
                    $value .= $c;
209
                }
210
                
211
                # Unexpected
212
                else {
213
                    croak qq/Parsing error. unexpected "{". / .
added tests
yuki-kimoto authored on 2010-08-12
214
                          qq/pos $pos of "$original"/;
removed DBIx::Custom::Query ...
yuki-kimoto authored on 2010-08-12
215
                }
216
            }
renamed default_query_filter...
yuki-kimoto authored on 2010-08-03
217
            
removed DBIx::Custom::Query ...
yuki-kimoto authored on 2010-08-12
218
            # Tag end charactor
219
            elsif ($c eq '}') {
220
                
221
                # Escaped charactor
222
                if ($before eq "\\") {
223
                    substr($value, -1, 1, '');
224
                    $value .= $c;
225
                }
226
                
227
                # Tag end
228
                else {
229
                
230
                    # Change state
231
                    $state = 'text';
232
                    
233
                    # Add tag
234
                    my ($tag_name, @tag_args) = split /\s+/, $value;
235
                    push @tree, {type => 'tag', tag_name => $tag_name, 
236
                                 tag_args => \@tag_args};
237
                    
238
                    # Clear
239
                    $value = '';
240
                }
241
            }
242
            
243
            # Normal charactor
244
            else { $value .= $c }
renamed default_query_filter...
yuki-kimoto authored on 2010-08-03
245
        }
removed DBIx::Custom::Query ...
yuki-kimoto authored on 2010-08-12
246
        
247
        # Save before charactor
248
        $before = $c;
249
        
250
        # increment position
251
        $pos++;
renamed default_query_filter...
yuki-kimoto authored on 2010-08-03
252
    }
253
    
removed DBIx::Custom::Query ...
yuki-kimoto authored on 2010-08-12
254
    # Tag not finished
added tests
yuki-kimoto authored on 2010-08-12
255
    croak qq{Tag not finished. "$original"}
removed DBIx::Custom::Query ...
yuki-kimoto authored on 2010-08-12
256
      if $state eq 'tag';
renamed default_query_filter...
yuki-kimoto authored on 2010-08-03
257
    
removed DBIx::Custom::Query ...
yuki-kimoto authored on 2010-08-12
258
    # Add rest text
259
    push @tree, {type => 'text', value => $value}
260
      if $value;
261
    
262
    return \@tree;
renamed default_query_filter...
yuki-kimoto authored on 2010-08-03
263
}
264

            
265
sub _placeholder_count {
266
    my ($self, $expand) = @_;
267
    
changed argument of tag proc...
yuki-kimoto authored on 2010-08-03
268
    # Count
269
    $expand ||= '';
renamed default_query_filter...
yuki-kimoto authored on 2010-08-03
270
    my $count = 0;
271
    my $pos   = -1;
272
    while (($pos = index($expand, '?', $pos + 1)) != -1) {
273
        $count++;
274
    }
275
    return $count;
276
}
277

            
renamed DBIx::Custom::TagPro...
Yuki Kimoto authored on 2011-01-24
278
# Follwoing methods are DEPRECATED!
279
__PACKAGE__->attr('tag_processors' => sub { {} });
280

            
281
sub register_tag_processor {
282
    my $self = shift;
283
    
284
    # Merge tag processor
285
    my $tag_processors = ref $_[0] eq 'HASH' ? $_[0] : {@_};
286
    $self->tag_processors({%{$self->tag_processors}, %{$tag_processors}});
287
    
288
    return $self;
289
}
290

            
renamed default_query_filter...
yuki-kimoto authored on 2010-08-03
291
1;
292

            
293
=head1 NAME
294

            
295
DBIx::Custom::QueryBuilder - Query builder
296

            
297
=head1 SYNOPSIS
298
    
299
    my $builder = DBIx::Custom::QueryBuilder->new;
remove DBIx::Custom::QueryBu...
yuki-kimoto authored on 2010-08-05
300
    my $query = $builder->build_query(
301
        "select from table {= k1} && {<> k2} || {like k3}"
302
    );
renamed default_query_filter...
yuki-kimoto authored on 2010-08-03
303

            
304
=head1 ATTRIBUTES
305

            
renamed DBIx::Custom::TagPro...
Yuki Kimoto authored on 2011-01-24
306
=head2 C<tags>
renamed default_query_filter...
yuki-kimoto authored on 2010-08-03
307

            
renamed DBIx::Custom::TagPro...
Yuki Kimoto authored on 2011-01-24
308
    my $tags = $builder->tags;
309
    $builder           = $builder->tags(\%tags);
renamed default_query_filter...
yuki-kimoto authored on 2010-08-03
310

            
311
Tag processors.
312

            
313
=head1 METHODS
314

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

            
318
=head2 C<build_query>
319
    
320
    my $query = $builder->build_query($source);
321

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

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

            
329
    'select * from books \\{ something statement \\}'
330

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

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

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

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

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

            
renamed DBIx::Custom::TagPro...
Yuki Kimoto authored on 2011-01-24
344
=head2 C<register_tag>
renamed default_query_filter...
yuki-kimoto authored on 2010-08-03
345

            
renamed DBIx::Custom::TagPro...
Yuki Kimoto authored on 2011-01-24
346
    $builder->register_tag(\%tags);
347
    $builder->register_tag(%tags);
renamed default_query_filter...
yuki-kimoto authored on 2010-08-03
348

            
349
Register tag processor.
350

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

            
renamed default_query_filter...
yuki-kimoto authored on 2010-08-03
353
    $builder->register_tag_processor(
354
        '?' => sub {
remove DBIx::Custom::QueryBu...
yuki-kimoto authored on 2010-08-05
355
            my $column = shift;
renamed default_query_filter...
yuki-kimoto authored on 2010-08-03
356
            
remove DBIx::Custom::QueryBu...
yuki-kimoto authored on 2010-08-05
357
            return ['?', [$column]];
renamed default_query_filter...
yuki-kimoto authored on 2010-08-03
358
        }
359
    );
360

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

            
363
=head1 Tags
364

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

            
367
=head2 C<?>
368

            
369
Placeholder tag.
370

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

            
373
=head2 C<=>
374

            
375
Equal tag.
376

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

            
379
=head2 C<E<lt>E<gt>>
380

            
381
Not equal tag.
382

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

            
385
=head2 C<E<lt>>
386

            
387
Lower than tag
388

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

            
391
=head2 C<E<gt>>
392

            
393
Greater than tag
394

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

            
397
=head2 C<E<gt>=>
398

            
399
Greater than or equal tag
400

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

            
403
=head2 C<E<lt>=>
404

            
405
Lower than or equal tag
406

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

            
409
=head2 C<like>
410

            
411
Like tag
412

            
413
    {like NAME}   ->   NAME like ?
414

            
415
=head2 C<in>
416

            
417
In tag.
418

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

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

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

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

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

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

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