DBIx-Custom / lib / DBIx / Custom / QueryBuilder.pm /
Newer Older
348 lines | 8.153kb
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;
10

            
fixed Carp trast relationshi...
yuki-kimoto authored on 2010-08-12
11
# Carp trust relationship
12
push @DBIx::Custom::CARP_NOT, __PACKAGE__;
updated document
Yuki Kimoto authored on 2011-01-20
13
push @DBIx::Custom::Where::CARP_NOT, __PACKAGE__;
14

            
cleanup
yuki-kimoto authored on 2010-10-17
15
# Attributes
cleanup
Yuki Kimoto authored on 2011-01-25
16
__PACKAGE__->attr('tags' => sub { {} });
renamed default_query_filter...
yuki-kimoto authored on 2010-08-03
17

            
cleanup
yuki-kimoto authored on 2010-10-17
18
sub build_query {
19
    my ($self, $source)  = @_;
20
    
21
    # Parse
22
    my $tree = $self->_parse($source);
23
    
24
    # Build query
25
    my $query = $self->_build_query($tree);
26
    
27
    return $query;
28
}
29

            
renamed DBIx::Custom::TagPro...
Yuki Kimoto authored on 2011-01-24
30
sub register_tag {
renamed default_query_filter...
yuki-kimoto authored on 2010-08-03
31
    my $self = shift;
changed argument of tag proc...
yuki-kimoto authored on 2010-08-03
32
    
cleanup
Yuki Kimoto authored on 2011-01-25
33
    # Merge tag
renamed DBIx::Custom::TagPro...
Yuki Kimoto authored on 2011-01-24
34
    my $tags = ref $_[0] eq 'HASH' ? $_[0] : {@_};
35
    $self->tags({%{$self->tags}, %$tags});
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

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

            
94
    # Check placeholder count
95
    my $placeholder_count = $self->_placeholder_count($sql);
96
    my $column_count      = @$all_columns;
97
    croak qq{Placeholder count in "$sql" must be same as column count $column_count}
98
      unless $placeholder_count eq @$all_columns;
99
    
100
    # Add semicolon
101
    $sql .= ';' unless $sql =~ /;$/;
102
    
103
    # Query
104
    my $query = DBIx::Custom::Query->new(sql => $sql, columns => $all_columns);
renamed default_query_filter...
yuki-kimoto authored on 2010-08-03
105
    
106
    return $query;
107
}
108

            
109
sub _parse {
110
    my ($self, $source) = @_;
111
    
remove DBIx::Custom::QueryBu...
yuki-kimoto authored on 2010-08-05
112
    # Source
renamed default_query_filter...
yuki-kimoto authored on 2010-08-03
113
    $source ||= '';
fixed tests
yuki-kimoto authored on 2010-08-06
114

            
remove DBIx::Custom::QueryBu...
yuki-kimoto authored on 2010-08-05
115
    # Tree
removed DBIx::Custom::Query ...
yuki-kimoto authored on 2010-08-12
116
    my @tree;
remove DBIx::Custom::QueryBu...
yuki-kimoto authored on 2010-08-05
117
    
removed DBIx::Custom::Query ...
yuki-kimoto authored on 2010-08-12
118
    # Value
119
    my $value = '';
renamed default_query_filter...
yuki-kimoto authored on 2010-08-03
120
    
removed DBIx::Custom::Query ...
yuki-kimoto authored on 2010-08-12
121
    # State
renamed default_query_filter...
yuki-kimoto authored on 2010-08-03
122
    my $state = 'text';
123
    
removed DBIx::Custom::Query ...
yuki-kimoto authored on 2010-08-12
124
    # Before charactor
125
    my $before = '';
126

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

            
250
sub _placeholder_count {
251
    my ($self, $expand) = @_;
252
    
changed argument of tag proc...
yuki-kimoto authored on 2010-08-03
253
    # Count
254
    $expand ||= '';
renamed default_query_filter...
yuki-kimoto authored on 2010-08-03
255
    my $count = 0;
256
    my $pos   = -1;
257
    while (($pos = index($expand, '?', $pos + 1)) != -1) {
258
        $count++;
259
    }
260
    return $count;
261
}
262

            
cleanup
Yuki Kimoto authored on 2011-01-25
263
# DEPRECATED!
renamed DBIx::Custom::TagPro...
Yuki Kimoto authored on 2011-01-24
264
__PACKAGE__->attr('tag_processors' => sub { {} });
265

            
cleanup
Yuki Kimoto authored on 2011-01-25
266
# DEPRECATED!
renamed DBIx::Custom::TagPro...
Yuki Kimoto authored on 2011-01-24
267
sub register_tag_processor {
268
    my $self = shift;
269
    
cleanup
Yuki Kimoto authored on 2011-01-25
270
    # Merge tag
renamed DBIx::Custom::TagPro...
Yuki Kimoto authored on 2011-01-24
271
    my $tag_processors = ref $_[0] eq 'HASH' ? $_[0] : {@_};
272
    $self->tag_processors({%{$self->tag_processors}, %{$tag_processors}});
273
    
274
    return $self;
275
}
276

            
renamed default_query_filter...
yuki-kimoto authored on 2010-08-03
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

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

            
renamed DBIx::Custom::TagPro...
Yuki Kimoto authored on 2011-01-24
294
    my $tags = $builder->tags;
cleanup
Yuki Kimoto authored on 2011-01-25
295
    $builder = $builder->tags(\%tags);
renamed default_query_filter...
yuki-kimoto authored on 2010-08-03
296

            
cleanup
Yuki Kimoto authored on 2011-01-25
297
Tags.
renamed default_query_filter...
yuki-kimoto authored on 2010-08-03
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

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

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

            
cleanup
Yuki Kimoto authored on 2011-01-25
335
Register tag.
renamed default_query_filter...
yuki-kimoto authored on 2010-08-03
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

            
cleanup
Yuki Kimoto authored on 2011-01-25
339
    $builder->register_tag(
renamed default_query_filter...
yuki-kimoto authored on 2010-08-03
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

            
cleanup
Yuki Kimoto authored on 2011-01-25
347
See also L<DBIx::Custom::Tag> to know tag.
update document
yuki-kimoto authored on 2010-08-07
348