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

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

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

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

            
274
1;
275

            
276
=head1 NAME
277

            
278
DBIx::Custom::QueryBuilder - Query builder
279

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

            
287
=head1 ATTRIBUTES
288

            
289
=head2 C<tag_processors>
290

            
291
    my $tag_processors = $builder->tag_processors;
292
    $builder           = $builder->tag_processors(\%tag_processors);
293

            
294
Tag processors.
295

            
296
=head1 METHODS
297

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

            
301
=head2 C<build_query>
302
    
303
    my $query = $builder->build_query($source);
304

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

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

            
312
    'select * from books \\{ something statement \\}'
313

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

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

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

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

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

            
327
=head2 C<register_tag_processor>
328

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

            
332
Register tag processor.
333

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

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

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

            
346
=head1 Tags
347

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

            
350
=head2 C<?>
351

            
352
Placeholder tag.
353

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

            
356
=head2 C<=>
357

            
358
Equal tag.
359

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

            
362
=head2 C<E<lt>E<gt>>
363

            
364
Not equal tag.
365

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

            
368
=head2 C<E<lt>>
369

            
370
Lower than tag
371

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

            
374
=head2 C<E<gt>>
375

            
376
Greater than tag
377

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

            
380
=head2 C<E<gt>=>
381

            
382
Greater than or equal tag
383

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

            
386
=head2 C<E<lt>=>
387

            
388
Lower than or equal tag
389

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

            
392
=head2 C<like>
393

            
394
Like tag
395

            
396
    {like NAME}   ->   NAME like ?
397

            
398
=head2 C<in>
399

            
400
In tag.
401

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

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

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

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

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

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

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