DBIx-Custom / lib / DBIx / Custom / QueryBuilder.pm /
Newer Older
417 lines | 10.033kb
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

            
cleanup
yuki-kimoto authored on 2010-10-17
15
# Attributes
renamed default_query_filter...
yuki-kimoto authored on 2010-08-03
16
__PACKAGE__->dual_attr('tag_processors', default => sub { {} }, inherit => 'hash_copy');
cleanup
yuki-kimoto authored on 2010-10-17
17

            
18
# Resister tag processor
renamed default_query_filter...
yuki-kimoto authored on 2010-08-03
19
__PACKAGE__->register_tag_processor(
renamed update tag to update...
yuki-kimoto authored on 2010-08-09
20
    '?'     => \&DBIx::Custom::QueryBuilder::TagProcessors::expand_placeholder_tag,
21
    '='     => \&DBIx::Custom::QueryBuilder::TagProcessors::expand_equal_tag,
22
    '<>'    => \&DBIx::Custom::QueryBuilder::TagProcessors::expand_not_equal_tag,
23
    '>'     => \&DBIx::Custom::QueryBuilder::TagProcessors::expand_greater_than_tag,
24
    '<'     => \&DBIx::Custom::QueryBuilder::TagProcessors::expand_lower_than_tag,
25
    '>='    => \&DBIx::Custom::QueryBuilder::TagProcessors::expand_greater_than_equal_tag,
26
    '<='    => \&DBIx::Custom::QueryBuilder::TagProcessors::expand_lower_than_equal_tag,
27
    'like'  => \&DBIx::Custom::QueryBuilder::TagProcessors::expand_like_tag,
28
    'in'    => \&DBIx::Custom::QueryBuilder::TagProcessors::expand_in_tag,
29
    'insert_param' => \&DBIx::Custom::QueryBuilder::TagProcessors::expand_insert_param_tag,
30
    'update_param' => \&DBIx::Custom::QueryBuilder::TagProcessors::expand_update_param_tag
renamed default_query_filter...
yuki-kimoto authored on 2010-08-03
31
);
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 default_query_filter...
yuki-kimoto authored on 2010-08-03
45
sub register_tag_processor {
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 default_query_filter...
yuki-kimoto authored on 2010-08-03
49
    my $tag_processors = ref $_[0] eq 'HASH' ? $_[0] : {@_};
50
    $self->tag_processors({%{$self->tag_processors}, %{$tag_processors}});
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
80
            my $tag_processor = $self->tag_processors->{$tag_name};
81
            
82
            # Tag processor is not registered
83
            croak qq{Tag "$tag_name" in "{a }" is not registered}
84
              unless $tag_processor;
85
            
86
            # Tag processor not sub reference
87
            croak qq{Tag processor "$tag_name" must be sub reference}
88
              unless ref $tag_processor eq 'CODE';
89
            
90
            # Execute tag processor
91
            my $r = $tag_processor->(@$tag_args);
92
            
93
            # Check tag processor return value
94
            croak qq{Tag processor "$tag_name" must return [STRING, ARRAY_REFERENCE]}
95
              unless ref $r eq 'ARRAY' && defined $r->[0] && ref $r->[1] eq 'ARRAY';
96
            
97
            # Part of SQL statement and colum names
98
            my ($part, $columns) = @$r;
99
            
100
            # Add columns
101
            push @$all_columns, @$columns;
102
            
103
            # Join part tag to SQL
104
            $sql .= $part;
105
        }
106
    }
107

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

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

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

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

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

            
277
1;
278

            
279
=head1 NAME
280

            
281
DBIx::Custom::QueryBuilder - Query builder
282

            
283
=head1 SYNOPSIS
284
    
285
    my $builder = DBIx::Custom::QueryBuilder->new;
remove DBIx::Custom::QueryBu...
yuki-kimoto authored on 2010-08-05
286
    my $query = $builder->build_query(
287
        "select from table {= k1} && {<> k2} || {like k3}"
288
    );
renamed default_query_filter...
yuki-kimoto authored on 2010-08-03
289

            
290
=head1 ATTRIBUTES
291

            
292
=head2 C<tag_processors>
293

            
294
    my $tag_processors = $builder->tag_processors;
295
    $builder           = $builder->tag_processors(\%tag_processors);
296

            
297
Tag processors.
298

            
299
=head1 METHODS
300

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

            
304
=head2 C<build_query>
305
    
306
    my $query = $builder->build_query($source);
307

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

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

            
315
    'select * from books \\{ something statement \\}'
316

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

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

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

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

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

            
330
=head2 C<register_tag_processor>
331

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

            
335
Register tag processor.
336

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

            
renamed default_query_filter...
yuki-kimoto authored on 2010-08-03
339
    $builder->register_tag_processor(
340
        '?' => sub {
remove DBIx::Custom::QueryBu...
yuki-kimoto authored on 2010-08-05
341
            my $column = shift;
renamed default_query_filter...
yuki-kimoto authored on 2010-08-03
342
            
remove DBIx::Custom::QueryBu...
yuki-kimoto authored on 2010-08-05
343
            return ['?', [$column]];
renamed default_query_filter...
yuki-kimoto authored on 2010-08-03
344
        }
345
    );
346

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

            
349
=head1 Tags
350

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

            
353
=head2 C<?>
354

            
355
Placeholder tag.
356

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

            
359
=head2 C<=>
360

            
361
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>E<gt>>
366

            
367
Not equal 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<lt>>
372

            
373
Lower 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 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<gt>=>
384

            
385
Greater 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<E<lt>=>
390

            
391
Lower than or equal tag
392

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

            
395
=head2 C<like>
396

            
397
Like tag
398

            
399
    {like NAME}   ->   NAME like ?
400

            
401
=head2 C<in>
402

            
403
In tag.
404

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

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

            
renamed update tag to update...
yuki-kimoto authored on 2010-08-09
409
Insert 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
    {insert_param NAME1 NAME2}   ->   (NAME1, NAME2) values (?, ?)
update document
yuki-kimoto authored on 2010-08-07
412

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

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

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