DBIx-Custom / lib / DBIx / Custom / QueryBuilder.pm /
Newer Older
288 lines | 7.483kb
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(
changed arguments of tag pro...
yuki-kimoto authored on 2010-08-05
14
    '?'      => \&DBIx::Custom::QueryBuilder::TagProcessors::placeholder,
15
    '='      => \&DBIx::Custom::QueryBuilder::TagProcessors::equal,
16
    '<>'     => \&DBIx::Custom::QueryBuilder::TagProcessors::not_equal,
17
    '>'      => \&DBIx::Custom::QueryBuilder::TagProcessors::greater_than,
18
    '<'      => \&DBIx::Custom::QueryBuilder::TagProcessors::lower_than,
19
    '>='     => \&DBIx::Custom::QueryBuilder::TagProcessors::greater_than_equal,
20
    '<='     => \&DBIx::Custom::QueryBuilder::TagProcessors::lower_than_equal,
21
    'like'   => \&DBIx::Custom::QueryBuilder::TagProcessors::like,
22
    'in'     => \&DBIx::Custom::QueryBuilder::TagProcessors::in,
23
    'insert' => \&DBIx::Custom::QueryBuilder::TagProcessors::insert,
24
    'update' => \&DBIx::Custom::QueryBuilder::TagProcessors::update
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 ||= '';
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
            
remove DBIx::Custom::QueryBu...
yuki-kimoto authored on 2010-08-05
89
            # Tag processor not registerd
90
            croak qq{Tag "$tag" in "$original" is not registerd}
91
               unless $self->tag_processors->{$tag_name};
renamed default_query_filter...
yuki-kimoto authored on 2010-08-03
92
            
93
            # Check tag arguments
94
            foreach my $tag_arg (@tag_args) {
changed argument of tag proc...
yuki-kimoto authored on 2010-08-03
95
            
renamed default_query_filter...
yuki-kimoto authored on 2010-08-03
96
                # Cannot cantain placehosder '?'
remove DBIx::Custom::QueryBu...
yuki-kimoto authored on 2010-08-05
97
                croak qq{Tag cannot contains "?"}
renamed default_query_filter...
yuki-kimoto authored on 2010-08-03
98
                  if $tag_arg =~ /\?/;
99
            }
100
            
101
            # Add tag to parsing tree
remove DBIx::Custom::QueryBu...
yuki-kimoto authored on 2010-08-05
102
            push @$tree, {type => 'tag', tag_name => $tag_name,
103
                          tag_args => [@tag_args]};
renamed default_query_filter...
yuki-kimoto authored on 2010-08-03
104
        }
105
    }
106
    
107
    # Add text to parsing tree 
remove DBIx::Custom::QueryBu...
yuki-kimoto authored on 2010-08-05
108
    push @$tree, {type => 'text', tag_args => [$source]}
109
      if $source;
renamed default_query_filter...
yuki-kimoto authored on 2010-08-03
110
    
111
    return $tree;
112
}
113

            
114
sub _build_query {
115
    my ($self, $tree) = @_;
116
    
117
    # SQL
118
    my $sql = '';
119
    
changed argument of tag proc...
yuki-kimoto authored on 2010-08-03
120
    # All Columns
renamed default_query_filter...
yuki-kimoto authored on 2010-08-03
121
    my $all_columns = [];
122
    
123
    # Build SQL 
124
    foreach my $node (@$tree) {
125
        
126
        # Get type, tag name, and arguments
127
        my $type     = $node->{type};
128
        my $tag_name = $node->{tag_name};
129
        my $tag_args = $node->{tag_args};
130
        
131
        # Text
132
        if ($type eq 'text') {
133
            # Join text
134
            $sql .= $tag_args->[0];
135
        }
136
        
137
        # Tag
138
        elsif ($type eq 'tag') {
139
            
140
            # Get tag processor
141
            my $tag_processor = $self->tag_processors->{$tag_name};
142
            
remove DBIx::Custom::QueryBu...
yuki-kimoto authored on 2010-08-05
143
            # Tag processor not sub reference
144
            croak qq{Tag processor "$tag_name" must be sub reference}
renamed default_query_filter...
yuki-kimoto authored on 2010-08-03
145
              unless ref $tag_processor eq 'CODE';
146
            
remove DBIx::Custom::QueryBu...
yuki-kimoto authored on 2010-08-05
147
            # Execute tag processor
148
            my ($part, $columns) = @{$tag_processor->(@$tag_args)};
renamed default_query_filter...
yuki-kimoto authored on 2010-08-03
149
            
150
            # Check tag processor return value
remove DBIx::Custom::QueryBu...
yuki-kimoto authored on 2010-08-05
151
            croak qq{Tag processor "$tag_name" must return [STRING, ARRAY_REFERENCE]}
152
              if !defined $part || ref $columns ne 'ARRAY';
renamed default_query_filter...
yuki-kimoto authored on 2010-08-03
153
            
154
            # Check placeholder count
remove DBIx::Custom::QueryBu...
yuki-kimoto authored on 2010-08-05
155
            croak qq{Count of Placeholders must be same as count of columns in "$tag_name"}
156
              unless $self->_placeholder_count($part) eq @$columns;
renamed default_query_filter...
yuki-kimoto authored on 2010-08-03
157
            
changed argument of tag proc...
yuki-kimoto authored on 2010-08-03
158
            # Add columns
renamed default_query_filter...
yuki-kimoto authored on 2010-08-03
159
            push @$all_columns, @$columns;
160
            
remove DBIx::Custom::QueryBu...
yuki-kimoto authored on 2010-08-05
161
            # Join part tag to SQL
162
            $sql .= $part;
renamed default_query_filter...
yuki-kimoto authored on 2010-08-03
163
        }
164
    }
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

            
remove DBIx::Custom::QueryBu...
yuki-kimoto authored on 2010-08-05
258
B<Examples:>
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

            
remove DBIx::Custom::QueryBu...
yuki-kimoto authored on 2010-08-05
268
See L<DBIx::Custom::QueryBuilder::TagProcessors> about tag processor.
renamed default_query_filter...
yuki-kimoto authored on 2010-08-03
269

            
270
=head1 Tags
271

            
remove DBIx::Custom::QueryBu...
yuki-kimoto authored on 2010-08-05
272
You can use the following tags in SQL source.
273
    
274
    [Tags]           [Replaced]
renamed default_query_filter...
yuki-kimoto authored on 2010-08-03
275
    {? NAME}    ->   ?
276
    {= NAME}    ->   NAME = ?
277
    {<> NAME}   ->   NAME <> ?
278
    
279
    {< NAME}    ->   NAME < ?
280
    {> NAME}    ->   NAME > ?
281
    {>= NAME}   ->   NAME >= ?
282
    {<= NAME}   ->   NAME <= ?
283
    
284
    {like NAME}       ->   NAME like ?
285
    {in NAME COUNT}   ->   NAME in [?, ?, ..]
286
    
remove DBIx::Custom::QueryBu...
yuki-kimoto authored on 2010-08-05
287
    {insert NAME1 NAME2}   ->   (NAME1, NAME2) values (?, ?)
288
    {update NAME1 NAME2}   ->   set NAME1 = ?, NAME2 = ?