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

            
12
__PACKAGE__->dual_attr('tag_processors', default => sub { {} }, inherit => 'hash_copy');
13
__PACKAGE__->register_tag_processor(
renamed update tag to update...
yuki-kimoto authored on 2010-08-09
14
    '?'     => \&DBIx::Custom::QueryBuilder::TagProcessors::expand_placeholder_tag,
15
    '='     => \&DBIx::Custom::QueryBuilder::TagProcessors::expand_equal_tag,
16
    '<>'    => \&DBIx::Custom::QueryBuilder::TagProcessors::expand_not_equal_tag,
17
    '>'     => \&DBIx::Custom::QueryBuilder::TagProcessors::expand_greater_than_tag,
18
    '<'     => \&DBIx::Custom::QueryBuilder::TagProcessors::expand_lower_than_tag,
19
    '>='    => \&DBIx::Custom::QueryBuilder::TagProcessors::expand_greater_than_equal_tag,
20
    '<='    => \&DBIx::Custom::QueryBuilder::TagProcessors::expand_lower_than_equal_tag,
21
    'like'  => \&DBIx::Custom::QueryBuilder::TagProcessors::expand_like_tag,
22
    'in'    => \&DBIx::Custom::QueryBuilder::TagProcessors::expand_in_tag,
23
    'insert_param' => \&DBIx::Custom::QueryBuilder::TagProcessors::expand_insert_param_tag,
24
    'update_param' => \&DBIx::Custom::QueryBuilder::TagProcessors::expand_update_param_tag
renamed default_query_filter...
yuki-kimoto authored on 2010-08-03
25
);
26

            
27
__PACKAGE__->attr(tag_start => '{');
28
__PACKAGE__->attr(tag_end   => '}');
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
renamed default_query_filter...
yuki-kimoto authored on 2010-08-03
59
    my $tree = [];
60
    
remove DBIx::Custom::QueryBu...
yuki-kimoto authored on 2010-08-05
61
    # Start tag
renamed default_query_filter...
yuki-kimoto authored on 2010-08-03
62
    my $tag_start = quotemeta $self->tag_start;
remove DBIx::Custom::QueryBu...
yuki-kimoto authored on 2010-08-05
63
    croak qq{tag_start must be a charactor}
64
      if !$tag_start || length $tag_start == 1;
65
    
66
    # End tag
renamed default_query_filter...
yuki-kimoto authored on 2010-08-03
67
    my $tag_end   = quotemeta $self->tag_end;
remove DBIx::Custom::QueryBu...
yuki-kimoto authored on 2010-08-05
68
    croak qq{tag_end must be a charactor}
69
      if !$tag_end || length $tag_end == 1;
renamed default_query_filter...
yuki-kimoto authored on 2010-08-03
70
    
71
    # Tokenize
72
    my $state = 'text';
73
    
remove DBIx::Custom::QueryBu...
yuki-kimoto authored on 2010-08-05
74
    # Save original source
renamed default_query_filter...
yuki-kimoto authored on 2010-08-03
75
    my $original = $source;
76
    
77
    # Parse
78
    while ($source =~ s/([^$tag_start]*?)$tag_start([^$tag_end].*?)$tag_end//sm) {
79
        my $text = $1;
80
        my $tag  = $2;
81
        
82
        # Parse tree
83
        push @$tree, {type => 'text', tag_args => [$text]} if $text;
84
        
85
        if ($tag) {
86
            # Get tag name and arguments
87
            my ($tag_name, @tag_args) = split /\s+/, $tag;
88
            
fixed tests
yuki-kimoto authored on 2010-08-06
89
            # Tag processor not registered
90
            croak qq{Tag "$tag_name" in "$original" is not registered}
remove DBIx::Custom::QueryBu...
yuki-kimoto authored on 2010-08-05
91
               unless $self->tag_processors->{$tag_name};
renamed default_query_filter...
yuki-kimoto authored on 2010-08-03
92
            
93
            # Add tag to parsing tree
remove DBIx::Custom::QueryBu...
yuki-kimoto authored on 2010-08-05
94
            push @$tree, {type => 'tag', tag_name => $tag_name,
95
                          tag_args => [@tag_args]};
renamed default_query_filter...
yuki-kimoto authored on 2010-08-03
96
        }
97
    }
98
    
99
    # Add text to parsing tree 
remove DBIx::Custom::QueryBu...
yuki-kimoto authored on 2010-08-05
100
    push @$tree, {type => 'text', tag_args => [$source]}
101
      if $source;
renamed default_query_filter...
yuki-kimoto authored on 2010-08-03
102
    
103
    return $tree;
104
}
105

            
106
sub _build_query {
107
    my ($self, $tree) = @_;
108
    
109
    # SQL
110
    my $sql = '';
111
    
changed argument of tag proc...
yuki-kimoto authored on 2010-08-03
112
    # All Columns
renamed default_query_filter...
yuki-kimoto authored on 2010-08-03
113
    my $all_columns = [];
114
    
115
    # Build SQL 
116
    foreach my $node (@$tree) {
117
        
118
        # Get type, tag name, and arguments
119
        my $type     = $node->{type};
120
        my $tag_name = $node->{tag_name};
121
        my $tag_args = $node->{tag_args};
122
        
123
        # Text
124
        if ($type eq 'text') {
125
            # Join text
126
            $sql .= $tag_args->[0];
127
        }
128
        
129
        # Tag
130
        elsif ($type eq 'tag') {
131
            
132
            # Get tag processor
133
            my $tag_processor = $self->tag_processors->{$tag_name};
134
            
remove DBIx::Custom::QueryBu...
yuki-kimoto authored on 2010-08-05
135
            # Tag processor not sub reference
136
            croak qq{Tag processor "$tag_name" must be sub reference}
renamed default_query_filter...
yuki-kimoto authored on 2010-08-03
137
              unless ref $tag_processor eq 'CODE';
138
            
remove DBIx::Custom::QueryBu...
yuki-kimoto authored on 2010-08-05
139
            # Execute tag processor
add tests
yuki-kimoto authored on 2010-08-10
140
            my $r;
141
            {
142
                local $Carp::CarpLevel += 1;
143
                $r = $tag_processor->(@$tag_args);
144
            }
renamed default_query_filter...
yuki-kimoto authored on 2010-08-03
145
            
146
            # Check tag processor return value
remove DBIx::Custom::QueryBu...
yuki-kimoto authored on 2010-08-05
147
            croak qq{Tag processor "$tag_name" must return [STRING, ARRAY_REFERENCE]}
fixed tests
yuki-kimoto authored on 2010-08-06
148
              unless ref $r eq 'ARRAY' && defined $r->[0] && ref $r->[1] eq 'ARRAY';
renamed default_query_filter...
yuki-kimoto authored on 2010-08-03
149
            
fixed tests
yuki-kimoto authored on 2010-08-06
150
            # Part of SQL statement and colum names
151
            my ($part, $columns) = @$r;
renamed default_query_filter...
yuki-kimoto authored on 2010-08-03
152
            
changed argument of tag proc...
yuki-kimoto authored on 2010-08-03
153
            # Add columns
renamed default_query_filter...
yuki-kimoto authored on 2010-08-03
154
            push @$all_columns, @$columns;
155
            
remove DBIx::Custom::QueryBu...
yuki-kimoto authored on 2010-08-05
156
            # Join part tag to SQL
157
            $sql .= $part;
renamed default_query_filter...
yuki-kimoto authored on 2010-08-03
158
        }
159
    }
fixed tests
yuki-kimoto authored on 2010-08-06
160

            
161
    # Check placeholder count
162
    my $placeholder_count = $self->_placeholder_count($sql);
163
    my $column_count      = @$all_columns;
164
    croak qq{Placeholder count in "$sql" must be same as column count $column_count}
165
      unless $placeholder_count eq @$all_columns;
renamed default_query_filter...
yuki-kimoto authored on 2010-08-03
166
    
167
    # Add semicolon
168
    $sql .= ';' unless $sql =~ /;$/;
169
    
170
    # Query
171
    my $query = DBIx::Custom::Query->new(sql => $sql, columns => $all_columns);
172
    
173
    return $query;
174
}
175

            
176
sub _placeholder_count {
177
    my ($self, $expand) = @_;
178
    
changed argument of tag proc...
yuki-kimoto authored on 2010-08-03
179
    # Count
180
    $expand ||= '';
renamed default_query_filter...
yuki-kimoto authored on 2010-08-03
181
    my $count = 0;
182
    my $pos   = -1;
183
    while (($pos = index($expand, '?', $pos + 1)) != -1) {
184
        $count++;
185
    }
186
    return $count;
187
}
188

            
189
1;
190

            
191
=head1 NAME
192

            
193
DBIx::Custom::QueryBuilder - Query builder
194

            
195
=head1 SYNOPSIS
196
    
197
    my $builder = DBIx::Custom::QueryBuilder->new;
remove DBIx::Custom::QueryBu...
yuki-kimoto authored on 2010-08-05
198
    my $query = $builder->build_query(
199
        "select from table {= k1} && {<> k2} || {like k3}"
200
    );
renamed default_query_filter...
yuki-kimoto authored on 2010-08-03
201

            
202
=head1 ATTRIBUTES
203

            
204
=head2 C<tag_processors>
205

            
206
    my $tag_processors = $builder->tag_processors;
207
    $builder           = $builder->tag_processors(\%tag_processors);
208

            
209
Tag processors.
210

            
211
=head2 C<tag_start>
212
    
213
    my $tag_start = $builder->tag_start;
214
    $builder      = $builder->tag_start('{');
215

            
remove DBIx::Custom::QueryBu...
yuki-kimoto authored on 2010-08-05
216
Tag start charactor.
217
Default to '{'.
renamed default_query_filter...
yuki-kimoto authored on 2010-08-03
218

            
219
=head2 C<tag_end>
220
    
221
    my $tag_end = $builder->tag_start;
222
    $builder    = $builder->tag_start('}');
223

            
remove DBIx::Custom::QueryBu...
yuki-kimoto authored on 2010-08-05
224
Tag end charactor.
225
Default to '}'.
renamed default_query_filter...
yuki-kimoto authored on 2010-08-03
226
    
227
=head1 METHODS
228

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

            
232
=head2 C<build_query>
233
    
234
    my $query = $builder->build_query($source);
235

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

            
239
B<Example:>
240

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

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

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

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

            
252
=head2 C<register_tag_processor>
253

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

            
257
Register tag processor.
258

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

            
renamed default_query_filter...
yuki-kimoto authored on 2010-08-03
261
    $builder->register_tag_processor(
262
        '?' => sub {
remove DBIx::Custom::QueryBu...
yuki-kimoto authored on 2010-08-05
263
            my $column = shift;
renamed default_query_filter...
yuki-kimoto authored on 2010-08-03
264
            
remove DBIx::Custom::QueryBu...
yuki-kimoto authored on 2010-08-05
265
            return ['?', [$column]];
renamed default_query_filter...
yuki-kimoto authored on 2010-08-03
266
        }
267
    );
268

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

            
271
=head1 Tags
272

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

            
275
=head2 C<?>
276

            
277
Placeholder tag.
278

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

            
281
=head2 C<=>
282

            
283
Equal tag.
284

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

            
287
=head2 C<E<lt>E<gt>>
288

            
289
Not equal tag.
290

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

            
293
=head2 C<E<lt>>
294

            
295
Lower than tag
296

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

            
299
=head2 C<E<gt>>
300

            
301
Greater than tag
302

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

            
305
=head2 C<E<gt>=>
306

            
307
Greater than or equal tag
308

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

            
311
=head2 C<E<lt>=>
312

            
313
Lower than or equal tag
314

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

            
317
=head2 C<like>
318

            
319
Like tag
320

            
321
    {like NAME}   ->   NAME like ?
322

            
323
=head2 C<in>
324

            
325
In tag.
326

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

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

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

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

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

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

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