DBIx-Custom / lib / DBIx / Custom / QueryBuilder.pm /
Newer Older
338 lines | 7.958kb
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
__PACKAGE__->attr(tag_start => '{');
31
__PACKAGE__->attr(tag_end   => '}');
32

            
33
sub register_tag_processor {
34
    my $self = shift;
changed argument of tag proc...
yuki-kimoto authored on 2010-08-03
35
    
remove DBIx::Custom::QueryBu...
yuki-kimoto authored on 2010-08-05
36
    # Merge tag processor
renamed default_query_filter...
yuki-kimoto authored on 2010-08-03
37
    my $tag_processors = ref $_[0] eq 'HASH' ? $_[0] : {@_};
38
    $self->tag_processors({%{$self->tag_processors}, %{$tag_processors}});
changed argument of tag proc...
yuki-kimoto authored on 2010-08-03
39
    
renamed default_query_filter...
yuki-kimoto authored on 2010-08-03
40
    return $self;
41
}
42

            
43
sub build_query {
44
    my ($self, $source)  = @_;
45
    
46
    # Parse
47
    my $tree = $self->_parse($source);
48
    
49
    # Build query
50
    my $query = $self->_build_query($tree);
51
    
52
    return $query;
53
}
54

            
55
sub _parse {
56
    my ($self, $source) = @_;
57
    
remove DBIx::Custom::QueryBu...
yuki-kimoto authored on 2010-08-05
58
    # Source
renamed default_query_filter...
yuki-kimoto authored on 2010-08-03
59
    $source ||= '';
fixed tests
yuki-kimoto authored on 2010-08-06
60

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

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

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

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

            
188
1;
189

            
190
=head1 NAME
191

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

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

            
201
=head1 ATTRIBUTES
202

            
203
=head2 C<tag_processors>
204

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

            
208
Tag processors.
209

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

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

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

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

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

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

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

            
238
B<Example:>
239

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

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

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

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

            
251
=head2 C<register_tag_processor>
252

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

            
256
Register tag processor.
257

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

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

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

            
270
=head1 Tags
271

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

            
274
=head2 C<?>
275

            
276
Placeholder tag.
277

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

            
280
=head2 C<=>
281

            
282
Equal tag.
283

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

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

            
288
Not equal tag.
289

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

            
292
=head2 C<E<lt>>
293

            
294
Lower than tag
295

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

            
298
=head2 C<E<gt>>
299

            
300
Greater than tag
301

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

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

            
306
Greater than or equal tag
307

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

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

            
312
Lower than or equal tag
313

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

            
316
=head2 C<like>
317

            
318
Like tag
319

            
320
    {like NAME}   ->   NAME like ?
321

            
322
=head2 C<in>
323

            
324
In tag.
325

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

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

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

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

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

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

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