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

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

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

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

            
276
1;
277

            
278
=head1 NAME
279

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

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

            
289
=head1 ATTRIBUTES
290

            
291
=head2 C<tag_processors>
292

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

            
296
Tag processors.
297

            
298
=head1 METHODS
299

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

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

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

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

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

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

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

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

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

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

            
329
=head2 C<register_tag_processor>
330

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

            
334
Register tag processor.
335

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

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

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

            
348
=head1 Tags
349

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

            
352
=head2 C<?>
353

            
354
Placeholder tag.
355

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

            
358
=head2 C<=>
359

            
360
Equal tag.
361

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

            
364
=head2 C<E<lt>E<gt>>
365

            
366
Not equal tag.
367

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

            
370
=head2 C<E<lt>>
371

            
372
Lower than tag
373

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

            
376
=head2 C<E<gt>>
377

            
378
Greater than tag
379

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

            
382
=head2 C<E<gt>=>
383

            
384
Greater than or equal tag
385

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

            
388
=head2 C<E<lt>=>
389

            
390
Lower than or equal tag
391

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

            
394
=head2 C<like>
395

            
396
Like tag
397

            
398
    {like NAME}   ->   NAME like ?
399

            
400
=head2 C<in>
401

            
402
In tag.
403

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

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

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

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

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

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

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