Showing 7 changed files with 400 additions and 459 deletions
+4
Changes
... ...
@@ -1,4 +1,8 @@
1 1
 0.1604
2
+  renamed default_query_filter to default_bind_filter(not backword compatible)
3
+  renamed DBIx::Custom::SQLTemplate to DBIx::Custom::SQLBuilder(not backword compatible)
4
+  renamed create_query to build_query(not backword compatible)
5
+  renamed sql_template to sql_builder(not backword compatible)
2 6
   removed DESTROY method(not backword compatible)
3 7
   removed disconnect method(not backword compatible)
4 8
   fixed DBIx::Custom::MySQL connect_memory
+65 -68
lib/DBIx/Custom.pm
... ...
@@ -8,13 +8,13 @@ use base 'Object::Simple';
8 8
 use Carp 'croak';
9 9
 use DBI;
10 10
 use DBIx::Custom::Result;
11
-use DBIx::Custom::SQLTemplate;
11
+use DBIx::Custom::QueryBuilder;
12 12
 use DBIx::Custom::Query;
13 13
 use Encode qw/encode_utf8 decode_utf8/;
14 14
 
15 15
 __PACKAGE__->attr('dbh');
16 16
 __PACKAGE__->attr([qw/user password data_source/]);
17
-__PACKAGE__->attr([qw/default_query_filter default_fetch_filter/]);
17
+__PACKAGE__->attr([qw/default_bind_filter default_fetch_filter/]);
18 18
 
19 19
 __PACKAGE__->dual_attr('filters', default => sub { {} },
20 20
                                   inherit => 'hash_copy');
... ...
@@ -24,7 +24,7 @@ __PACKAGE__->register_filter(
24 24
 );
25 25
 
26 26
 __PACKAGE__->attr(result_class => 'DBIx::Custom::Result');
27
-__PACKAGE__->attr(sql_template => sub { DBIx::Custom::SQLTemplate->new });
27
+__PACKAGE__->attr(sql_builder  => sub {DBIx::Custom::QueryBuilder->new});
28 28
 
29 29
 __PACKAGE__->attr(cache => 1);
30 30
 __PACKAGE__->attr(cache_method => sub {
... ...
@@ -109,12 +109,12 @@ sub insert {
109 109
       unless @insert_keys;
110 110
     
111 111
     # Templte for insert
112
-    my $template = "insert into $table {insert "
112
+    my $source = "insert into $table {insert "
113 113
                    . join(' ', @insert_keys) . '}';
114
-    $template .= " $append" if $append;
114
+    $source .= " $append" if $append;
115 115
     
116 116
     # Execute query
117
-    my $ret_val = $self->execute($template, param  => $param, 
117
+    my $ret_val = $self->execute($source, param  => $param, 
118 118
                                             filter => $filter);
119 119
     
120 120
     return $ret_val;
... ...
@@ -171,8 +171,8 @@ sub update {
171 171
     }
172 172
     
173 173
     # Template for update
174
-    my $template = "update $table $update_clause $where_clause";
175
-    $template .= " $append_statement" if $append_statement;
174
+    my $source = "update $table $update_clause $where_clause";
175
+    $source .= " $append_statement" if $append_statement;
176 176
     
177 177
     # Rearrange parammeters
178 178
     foreach my $wkey (@where_keys) {
... ...
@@ -189,7 +189,7 @@ sub update {
189 189
     }
190 190
     
191 191
     # Execute query
192
-    my $ret_val = $self->execute($template, param  => $param, 
192
+    my $ret_val = $self->execute($source, param  => $param, 
193 193
                                             filter => $filter);
194 194
     
195 195
     return $ret_val;
... ...
@@ -234,11 +234,11 @@ sub delete {
234 234
     }
235 235
     
236 236
     # Template for delete
237
-    my $template = "delete from $table $where_clause";
238
-    $template .= " $append_statement" if $append_statement;
237
+    my $source = "delete from $table $where_clause";
238
+    $source .= " $append_statement" if $append_statement;
239 239
     
240 240
     # Execute query
241
-    my $ret_val = $self->execute($template, param  => $where, 
241
+    my $ret_val = $self->execute($source, param  => $where, 
242 242
                                             filter => $filter);
243 243
     
244 244
     return $ret_val;
... ...
@@ -269,82 +269,83 @@ sub select {
269 269
     my $param    = $args{param} || {};
270 270
     
271 271
     # SQL template for select statement
272
-    my $template = 'select ';
272
+    my $source = 'select ';
273 273
     
274 274
     # Column clause
275 275
     if (@$columns) {
276 276
         foreach my $column (@$columns) {
277
-            $template .= "$column, ";
277
+            $source .= "$column, ";
278 278
         }
279
-        $template =~ s/, $/ /;
279
+        $source =~ s/, $/ /;
280 280
     }
281 281
     else {
282
-        $template .= '* ';
282
+        $source .= '* ';
283 283
     }
284 284
     
285 285
     # Table
286
-    $template .= 'from ';
286
+    $source .= 'from ';
287 287
     foreach my $table (@$tables) {
288
-        $template .= "$table, ";
288
+        $source .= "$table, ";
289 289
     }
290
-    $template =~ s/, $/ /;
290
+    $source =~ s/, $/ /;
291 291
     
292 292
     # Where clause
293 293
     my @where_keys = keys %$where;
294 294
     if (@where_keys) {
295
-        $template .= 'where ';
295
+        $source .= 'where ';
296 296
         foreach my $where_key (@where_keys) {
297
-            $template .= "{= $where_key} and ";
297
+            $source .= "{= $where_key} and ";
298 298
         }
299 299
     }
300
-    $template =~ s/ and $//;
300
+    $source =~ s/ and $//;
301 301
     
302 302
     # Relation
303 303
     if ($relation) {
304
-        $template .= @where_keys ? "and " : "where ";
304
+        $source .= @where_keys ? "and " : "where ";
305 305
         foreach my $rkey (keys %$relation) {
306
-            $template .= "$rkey = " . $relation->{$rkey} . " and ";
306
+            $source .= "$rkey = " . $relation->{$rkey} . " and ";
307 307
         }
308 308
     }
309
-    $template =~ s/ and $//;
309
+    $source =~ s/ and $//;
310 310
     
311 311
     # Append some statement
312
-    $template .= " $append" if $append;
312
+    $source .= " $append" if $append;
313 313
     
314 314
     # Execute query
315
-    my $result = $self->execute($template, param  => $where, 
315
+    my $result = $self->execute($source, param  => $where, 
316 316
                                            filter => $filter);
317 317
     
318 318
     return $result;
319 319
 }
320 320
 
321
-sub create_query {
322
-    my ($self, $template) = @_;
323
-    
324
-    # Create query from SQL template
325
-    my $sql_template = $self->sql_template;
321
+sub build_query {
322
+    my ($self, $source) = @_;
326 323
     
324
+    # Cache
327 325
     my $cache = $self->cache;
328 326
     
329 327
     # Create query
330 328
     my $query;
331 329
     if ($cache) {
332 330
         
333
-        # Cached query
334
-        my $q = $self->cache_method->($self, $template);
331
+        # Get query
332
+        my $q = $self->cache_method->($self, $source);
335 333
         
336 334
         # Create query
337 335
         $query = DBIx::Custom::Query->new($q) if $q;
338 336
     }
339 337
     
340 338
     unless ($query) {
339
+
340
+        # Create SQL object
341
+        my $builder = $self->sql_builder;
341 342
         
342 343
         # Create query
343
-        $query = eval{$sql_template->create_query($template)};
344
+        $query = eval{$builder->build_query($source)};
344 345
         croak($@) if $@;
345 346
         
346 347
         # Cache query
347
-        $self->cache_method->($self, $template,
348
+        $self->cache_method->($self, $source,
348 349
                              {sql     => $query->sql, 
349 350
                               columns => $query->columns})
350 351
           if $cache;
... ...
@@ -375,14 +376,14 @@ sub execute{
375 376
     
376 377
     # First argument is SQL template
377 378
     unless (ref $query eq 'DBIx::Custom::Query') {
378
-        my $template;
379
+        my $source;
379 380
         
380 381
         if (ref $query eq 'ARRAY') {
381
-            $template = $query->[0];
382
+            $source = $query->[0];
382 383
         }
383
-        else { $template = $query }
384
+        else { $source = $query }
384 385
         
385
-        $query = $self->create_query($template);
386
+        $query = $self->build_query($source);
386 387
     }
387 388
     
388 389
     my $filter = $args{filter} || $query->filter || {};
... ...
@@ -398,15 +399,13 @@ sub execute{
398 399
     # Return resultset if select statement is executed
399 400
     if ($sth->{NUM_OF_FIELDS}) {
400 401
         
401
-        # Get result class
402
-        my $result_class = $self->result_class;
403
-        
404 402
         # Create result
405
-        my $result = $result_class->new({
406
-            sth             => $sth,
407
-            default_filter  => $self->default_fetch_filter,
408
-            filters         => $self->filters
409
-        });
403
+        my $result = $self->result_class->new(
404
+            sth            => $sth,
405
+            default_filter => $self->default_fetch_filter,
406
+            filters        => $self->filters
407
+        );
408
+
410 409
         return $result;
411 410
     }
412 411
     return $affected;
... ...
@@ -434,7 +433,7 @@ sub _build_bind_values {
434 433
         $filter ||= {};
435 434
         
436 435
         # Filter name
437
-        my $fname = $filter->{$column} || $self->default_query_filter || '';
436
+        my $fname = $filter->{$column} || $self->default_bind_filter || '';
438 437
         
439 438
         my $filter_func;
440 439
         if ($fname) {
... ...
@@ -530,13 +529,13 @@ This module is not stable. Method name and implementations will be changed.
530 529
                   filter => {tilte => 'encode_utf8'});
531 530
 
532 531
     # Create query and execute it
533
-    my $query = $dbi->create_query(
532
+    my $query = $dbi->build_query(
534 533
         "select id from books where {= author} && {like title}"
535 534
     );
536 535
     $dbi->execute($query, param => {author => 'ken', title => '%Perl%'})
537 536
     
538 537
     # Default filter
539
-    $dbi->default_query_filter('encode_utf8');
538
+    $dbi->default_bind_filter('encode_utf8');
540 539
     $dbi->default_fetch_filter('decode_utf8');
541 540
     
542 541
     # Fetch
... ...
@@ -635,10 +634,10 @@ By default, "encode_utf8" and "decode_utf8" is registered.
635 634
     $encode_utf8 = $dbi->filters->{encode_utf8};
636 635
     $decode_utf8 = $dbi->filters->{decode_utf8};
637 636
 
638
-=head2 C<default_query_filter>
637
+=head2 C<default_bind_filter>
639 638
 
640
-    $dbi                  = $dbi->default_query_filter('encode_utf8');
641
-    $default_query_filter = $dbi->default_query_filter
639
+    $dbi                 = $dbi->default_bind_filter('encode_utf8');
640
+    $default_bind_filter = $dbi->default_bind_filter
642 641
 
643 642
 Default filter for value binding
644 643
 
... ...
@@ -655,16 +654,16 @@ Default filter for fetching.
655 654
     $result_class = $dbi->result_class;
656 655
 
657 656
 Result class for select statement.
658
-Default to L<DBIx::Custom::Result>
657
+Default to L<DBIx::Custom::Result>.
659 658
 
660
-=head2 C<sql_template>
659
+=head2 C<sql_builder>
661 660
 
662
-    $dbi          = $dbi->sql_template(DBIx::Cutom::SQLTemplate->new);
663
-    $sql_template = $dbi->sql_template;
661
+    $dbi       = $dbi->sql_builder('DBIx::Cutom::QueryBuilder);
662
+    $sql_class = $dbi->sql_builder;
664 663
 
665
-SQLTemplate instance. sql_template attribute must be 
666
-the instance of L<DBIx::Cutom::SQLTemplate> subclass.
667
-Default to DBIx::Cutom::SQLTemplate object
664
+SQL builder. sql_builder_class must be 
665
+the instance of L<DBIx::Cutom::QueryBuilder> subclass
666
+Default to DBIx::Custom::QueryBuilder.
668 667
 
669 668
 =head1 METHODS
670 669
 
... ...
@@ -796,19 +795,19 @@ B<Example:>
796 795
         relation => {'books.id' => 'rental.book_id'}
797 796
     );
798 797
 
799
-=head2 C<create_query>
798
+=head2 C<build_query>
800 799
     
801
-    my $query = $dbi->create_query(
800
+    my $query = $dbi->build_query(
802 801
         "select * from authors where {= name} and {= age};"
803 802
     );
804 803
 
805
-Create the instance of L<DBIx::Custom::Query>. 
806
-This receive the string written by SQL template.
804
+Build the instance of L<DBIx::Custom::Query>
805
+using L<DBIx::Custom::QueryBuilder>.
807 806
 
808 807
 =head2 C<execute>
809 808
 
810 809
     $result = $dbi->execute($query,    param => $params, filter => {%filter});
811
-    $result = $dbi->execute($template, param => $params, filter => {%filter});
810
+    $result = $dbi->execute($source, param => $params, filter => {%filter});
812 811
 
813 812
 Execute the instace of L<DBIx::Custom::Query> or
814 813
 the string written by SQL template.
... ...
@@ -823,8 +822,6 @@ B<Example:>
823 822
         # do something
824 823
     }
825 824
 
826
-See also L<DBIx::Custom::SQLTemplate> to know how to write SQL template.
827
-
828 825
 =head2 C<register_filter>
829 826
 
830 827
     $dbi->register_filter(%filters);
+314
lib/DBIx/Custom/QueryBuilder.pm
... ...
@@ -0,0 +1,314 @@
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
+use DBIx::Custom::QueryBuilder::TagProcessor;
11
+
12
+__PACKAGE__->dual_attr('tag_processors', default => sub { {} }, inherit => 'hash_copy');
13
+__PACKAGE__->register_tag_processor(
14
+    '?'      => \&DBIx::Custom::QueryBuilder::TagProcessors::expand_placeholder_tag,
15
+    '='      => \&DBIx::Custom::QueryBuilder::TagProcessors::expand_basic_tag,
16
+    '<>'     => \&DBIx::Custom::QueryBuilder::TagProcessors::expand_basic_tag,
17
+    '>'      => \&DBIx::Custom::QueryBuilder::TagProcessors::expand_basic_tag,
18
+    '<'      => \&DBIx::Custom::QueryBuilder::TagProcessors::expand_basic_tag,
19
+    '>='     => \&DBIx::Custom::QueryBuilder::TagProcessors::expand_basic_tag,
20
+    '<='     => \&DBIx::Custom::QueryBuilder::TagProcessors::expand_basic_tag,
21
+    'like'   => \&DBIx::Custom::QueryBuilder::TagProcessors::expand_basic_tag,
22
+    'in'     => \&DBIx::Custom::QueryBuilder::TagProcessors::expand_in_tag,
23
+    'insert' => \&DBIx::Custom::QueryBuilder::TagProcessors::expand_insert_tag,
24
+    'update' => \&DBIx::Custom::QueryBuilder::TagProcessors::expand_update_tag
25
+);
26
+
27
+__PACKAGE__->attr(tag_start => '{');
28
+__PACKAGE__->attr(tag_end   => '}');
29
+
30
+__PACKAGE__->attr('tag_syntax' => <<'EOS');
31
+[tag]                     [expand]
32
+{? name}                  ?
33
+{= name}                  name = ?
34
+{<> name}                 name <> ?
35
+
36
+{< name}                  name < ?
37
+{> name}                  name > ?
38
+{>= name}                 name >= ?
39
+{<= name}                 name <= ?
40
+
41
+{like name}               name like ?
42
+{in name number}          name in [?, ?, ..]
43
+
44
+{insert key1 key2} (key1, key2) values (?, ?)
45
+{update key1 key2}    set key1 = ?, key2 = ?
46
+EOS
47
+
48
+sub register_tag_processor {
49
+    my $self = shift;
50
+    my $tag_processors = ref $_[0] eq 'HASH' ? $_[0] : {@_};
51
+    $self->tag_processors({%{$self->tag_processors}, %{$tag_processors}});
52
+    return $self;
53
+}
54
+
55
+sub build_query {
56
+    my ($self, $source)  = @_;
57
+    
58
+    # Parse
59
+    my $tree = $self->_parse($source);
60
+    
61
+    # Build query
62
+    my $query = $self->_build_query($tree);
63
+    
64
+    return $query;
65
+}
66
+
67
+sub _parse {
68
+    my ($self, $source) = @_;
69
+    
70
+    if (ref $source eq 'ARRAY') {
71
+        $source = $source->[1];
72
+    }
73
+    $source ||= '';
74
+    
75
+    my $tree = [];
76
+    
77
+    # Tags
78
+    my $tag_start = quotemeta $self->tag_start;
79
+    my $tag_end   = quotemeta $self->tag_end;
80
+    
81
+    # Tokenize
82
+    my $state = 'text';
83
+    
84
+    # Save original
85
+    my $original = $source;
86
+    
87
+    # Parse
88
+    while ($source =~ s/([^$tag_start]*?)$tag_start([^$tag_end].*?)$tag_end//sm) {
89
+        my $text = $1;
90
+        my $tag  = $2;
91
+        
92
+        # Parse tree
93
+        push @$tree, {type => 'text', tag_args => [$text]} if $text;
94
+        
95
+        if ($tag) {
96
+            # Get tag name and arguments
97
+            my ($tag_name, @tag_args) = split /\s+/, $tag;
98
+            
99
+            # Tag processor is exist?
100
+            unless ($self->tag_processors->{$tag_name}) {
101
+                my $tag_syntax = $self->tag_syntax;
102
+                croak("Tag '{$tag}' is not registerd.\n\n" .
103
+                      "<SQL builder syntax>\n" .
104
+                      "$tag_syntax\n" .
105
+                      "<Your source>\n" .
106
+                      "$original\n\n");
107
+            }
108
+            
109
+            # Check tag arguments
110
+            foreach my $tag_arg (@tag_args) {
111
+                # Cannot cantain placehosder '?'
112
+                croak("Tag '{t }' arguments cannot contain '?'")
113
+                  if $tag_arg =~ /\?/;
114
+            }
115
+            
116
+            # Add tag to parsing tree
117
+            push @$tree, {type => 'tag', tag_name => $tag_name, tag_args => [@tag_args]};
118
+        }
119
+    }
120
+    
121
+    # Add text to parsing tree 
122
+    push @$tree, {type => 'text', tag_args => [$source]} if $source;
123
+    
124
+    return $tree;
125
+}
126
+
127
+sub _build_query {
128
+    my ($self, $tree) = @_;
129
+    
130
+    # SQL
131
+    my $sql = '';
132
+    
133
+    # All parameter key infomation
134
+    my $all_columns = [];
135
+    
136
+    # Build SQL 
137
+    foreach my $node (@$tree) {
138
+        
139
+        # Get type, tag name, and arguments
140
+        my $type     = $node->{type};
141
+        my $tag_name = $node->{tag_name};
142
+        my $tag_args = $node->{tag_args};
143
+        
144
+        # Text
145
+        if ($type eq 'text') {
146
+            # Join text
147
+            $sql .= $tag_args->[0];
148
+        }
149
+        
150
+        # Tag
151
+        elsif ($type eq 'tag') {
152
+            
153
+            # Get tag processor
154
+            my $tag_processor = $self->tag_processors->{$tag_name};
155
+            
156
+            # Tag processor is code ref?
157
+            croak("Tag processor '$tag_name' must be code reference")
158
+              unless ref $tag_processor eq 'CODE';
159
+            
160
+            # Expand tag using tag processor
161
+            my ($expand, $columns) = $tag_processor->($tag_name, $tag_args);
162
+            
163
+            # Check tag processor return value
164
+            croak("Tag processor '$tag_name' must return (\$expand, \$columns)")
165
+              if !defined $expand || ref $columns ne 'ARRAY';
166
+            
167
+            # Check placeholder count
168
+            croak("Placeholder count in SQL created by tag processor '$tag_name' " .
169
+                  "must be same as key informations count")
170
+              unless $self->_placeholder_count($expand) eq @$columns;
171
+            
172
+            # Add key information
173
+            push @$all_columns, @$columns;
174
+            
175
+            # Join expand tag to SQL
176
+            $sql .= $expand;
177
+        }
178
+    }
179
+    
180
+    # Add semicolon
181
+    $sql .= ';' unless $sql =~ /;$/;
182
+    
183
+    # Query
184
+    my $query = DBIx::Custom::Query->new(sql => $sql, columns => $all_columns);
185
+    
186
+    return $query;
187
+}
188
+
189
+sub _placeholder_count {
190
+    my ($self, $expand) = @_;
191
+    $expand ||= '';
192
+    
193
+    my $count = 0;
194
+    my $pos   = -1;
195
+    while (($pos = index($expand, '?', $pos + 1)) != -1) {
196
+        $count++;
197
+    }
198
+    return $count;
199
+}
200
+
201
+1;
202
+
203
+=head1 NAME
204
+
205
+DBIx::Custom::QueryBuilder - Query builder
206
+
207
+=head1 SYNOPSIS
208
+    
209
+    my $builder = DBIx::Custom::QueryBuilder->new;
210
+    
211
+    my $source = "select from table {= k1} && {<> k2} || {like k3}";
212
+    my $param = {k1 => 1, k2 => 2, k3 => 3};
213
+    
214
+    my $query = $sql_builder->build_query($source);
215
+
216
+=head1 ATTRIBUTES
217
+
218
+=head2 C<tag_processors>
219
+
220
+    my $tag_processors = $builder->tag_processors;
221
+    $builder           = $builder->tag_processors(\%tag_processors);
222
+
223
+Tag processors.
224
+
225
+=head2 C<tag_start>
226
+    
227
+    my $tag_start = $builder->tag_start;
228
+    $builder      = $builder->tag_start('{');
229
+
230
+String of tag start.
231
+Default to '{'
232
+
233
+=head2 C<tag_end>
234
+    
235
+    my $tag_end = $builder->tag_start;
236
+    $builder    = $builder->tag_start('}');
237
+
238
+String of tag end.
239
+Default to '}'
240
+    
241
+=head2 C<tag_syntax>
242
+    
243
+    my $tag_syntax = $builder->tag_syntax;
244
+    $builder       = $builder->tag_syntax($tag_syntax);
245
+
246
+Tag syntax.
247
+
248
+=head1 METHODS
249
+
250
+This class is L<Object::Simple> subclass.
251
+You can use all methods of L<Object::Simple>
252
+
253
+=head2 C<new>
254
+
255
+    my $builder = DBIx::Custom::SQLBuilder->new;
256
+    my $builder = DBIx::Custom::SQLBuilder->new(%attrs);
257
+    my $builder = DBIx::Custom::SQLBuilder->new(\%attrs);
258
+
259
+Create a instance.
260
+
261
+=head2 C<build_query>
262
+    
263
+    my $query = $builder->build_query($source);
264
+
265
+Build L<DBIx::Custom::Query> object.
266
+
267
+B<Example:>
268
+
269
+Source:
270
+
271
+    my $query = $builder->build_query(
272
+      "select * from table where {= title} && {like author} || {<= price}")
273
+
274
+Query:
275
+
276
+    $qeury->sql : "select * from table where title = ? && author like ? price <= ?;"
277
+    $query->columns : ['title', 'author', 'price']
278
+
279
+=head2 C<register_tag_processor>
280
+
281
+    $builder = $builder->register_tag_processor($tag_processor);
282
+
283
+Register tag processor.
284
+
285
+    $builder->register_tag_processor(
286
+        '?' => sub {
287
+            my $args = shift;
288
+            
289
+            # Do something
290
+            
291
+            # Expanded tag and column names
292
+            return ($expand, $columns);
293
+        }
294
+    );
295
+
296
+Tag processor receive arguments in tags
297
+and must return expanded tag and column names.
298
+
299
+=head1 Tags
300
+
301
+    {? NAME}    ->   ?
302
+    {= NAME}    ->   NAME = ?
303
+    {<> NAME}   ->   NAME <> ?
304
+    
305
+    {< NAME}    ->   NAME < ?
306
+    {> NAME}    ->   NAME > ?
307
+    {>= NAME}   ->   NAME >= ?
308
+    {<= NAME}   ->   NAME <= ?
309
+    
310
+    {like NAME}       ->   NAME like ?
311
+    {in NAME COUNT}   ->   NAME in [?, ?, ..]
312
+    
313
+    {insert NAME1 NAME2 NAME3}   ->   (NAME1, NAME2, NAME3) values (?, ?, ?)
314
+    {update NAME1 NAME2 NAME3}   ->   set NAME1 = ?, NAME2 = ?, NAME3 = ?
+2 -2
lib/DBIx/Custom/SQLTemplate/TagProcessor.pm → lib/DBIx/Custom/QueryBuilder/TagProcessor.pm
... ...
@@ -1,4 +1,4 @@
1
-package DBIx::Custom::SQLTemplate::TagProcessors;
1
+package DBIx::Custom::QueryBuilder::TagProcessors;
2 2
 
3 3
 use strict;
4 4
 use warnings;
... ...
@@ -117,7 +117,7 @@ sub expand_update_tag {
117 117
 
118 118
 =head1 NAME
119 119
 
120
-DBIx::Custom::SQLTemplate::TagProcessor - Tag processor
120
+DBIx::Custom::SQLBuilder::TagProcessors - Tag processor
121 121
 
122 122
 =head1 FUNCTIONS
123 123
 
-374
lib/DBIx/Custom/SQLTemplate.pm
... ...
@@ -1,374 +0,0 @@
1
-package DBIx::Custom::SQLTemplate;
2
-
3
-use strict;
4
-use warnings;
5
-
6
-use base 'Object::Simple';
7
-
8
-use Carp 'croak';
9
-use DBIx::Custom::Query;
10
-use DBIx::Custom::SQLTemplate::TagProcessor;
11
-
12
-__PACKAGE__->attr('tag_processors' => sub { {} });
13
-
14
-__PACKAGE__->attr(tag_start => '{');
15
-__PACKAGE__->attr(tag_end   => '}');
16
-
17
-__PACKAGE__->attr('tag_syntax' => <<'EOS');
18
-[tag]                     [expand]
19
-{? name}                  ?
20
-{= name}                  name = ?
21
-{<> name}                 name <> ?
22
-
23
-{< name}                  name < ?
24
-{> name}                  name > ?
25
-{>= name}                 name >= ?
26
-{<= name}                 name <= ?
27
-
28
-{like name}               name like ?
29
-{in name number}          name in [?, ?, ..]
30
-
31
-{insert key1 key2} (key1, key2) values (?, ?)
32
-{update key1 key2}    set key1 = ?, key2 = ?
33
-EOS
34
-
35
-sub new {
36
-    my $self = shift->SUPER::new;
37
-    
38
-    $self->register_tag_processor(
39
-        '?'      => \&DBIx::Custom::SQLTemplate::TagProcessors::expand_placeholder_tag,
40
-        '='      => \&DBIx::Custom::SQLTemplate::TagProcessors::expand_basic_tag,
41
-        '<>'     => \&DBIx::Custom::SQLTemplate::TagProcessors::expand_basic_tag,
42
-        '>'      => \&DBIx::Custom::SQLTemplate::TagProcessors::expand_basic_tag,
43
-        '<'      => \&DBIx::Custom::SQLTemplate::TagProcessors::expand_basic_tag,
44
-        '>='     => \&DBIx::Custom::SQLTemplate::TagProcessors::expand_basic_tag,
45
-        '<='     => \&DBIx::Custom::SQLTemplate::TagProcessors::expand_basic_tag,
46
-        'like'   => \&DBIx::Custom::SQLTemplate::TagProcessors::expand_basic_tag,
47
-        'in'     => \&DBIx::Custom::SQLTemplate::TagProcessors::expand_in_tag,
48
-        'insert' => \&DBIx::Custom::SQLTemplate::TagProcessors::expand_insert_tag,
49
-        'update' => \&DBIx::Custom::SQLTemplate::TagProcessors::expand_update_tag
50
-    );
51
-    
52
-    return $self;
53
-}
54
-
55
-sub register_tag_processor {
56
-    my $self = shift;
57
-    my $tag_processors = ref $_[0] eq 'HASH' ? $_[0] : {@_};
58
-    $self->tag_processors({%{$self->tag_processors}, %{$tag_processors}});
59
-    return $self;
60
-}
61
-
62
-sub create_query {
63
-    my ($self, $template)  = @_;
64
-    
65
-    # Parse template
66
-    my $tree = $self->_parse_template($template);
67
-    
68
-    # Build query
69
-    my $query = $self->_build_query($tree);
70
-    
71
-    return $query;
72
-}
73
-
74
-sub _parse_template {
75
-    my ($self, $template) = @_;
76
-    
77
-    if (ref $template eq 'ARRAY') {
78
-        $template = $template->[1];
79
-    }
80
-    $template ||= '';
81
-    
82
-    my $tree = [];
83
-    
84
-    # Tags
85
-    my $tag_start = quotemeta $self->tag_start;
86
-    my $tag_end   = quotemeta $self->tag_end;
87
-    
88
-    # Tokenize
89
-    my $state = 'text';
90
-    
91
-    # Save original template
92
-    my $original_template = $template;
93
-    
94
-    # Parse template
95
-    while ($template =~ s/([^$tag_start]*?)$tag_start([^$tag_end].*?)$tag_end//sm) {
96
-        my $text = $1;
97
-        my $tag  = $2;
98
-        
99
-        # Parse tree
100
-        push @$tree, {type => 'text', tag_args => [$text]} if $text;
101
-        
102
-        if ($tag) {
103
-            # Get tag name and arguments
104
-            my ($tag_name, @tag_args) = split /\s+/, $tag;
105
-            
106
-            # Tag processor is exist?
107
-            unless ($self->tag_processors->{$tag_name}) {
108
-                my $tag_syntax = $self->tag_syntax;
109
-                croak("Tag '{$tag}' in SQL template is not exist.\n\n" .
110
-                      "<SQL template tag syntax>\n" .
111
-                      "$tag_syntax\n" .
112
-                      "<Your SQL template>\n" .
113
-                      "$original_template\n\n");
114
-            }
115
-            
116
-            # Check tag arguments
117
-            foreach my $tag_arg (@tag_args) {
118
-                # Cannot cantain placehosder '?'
119
-                croak("Tag '{t }' arguments cannot contain '?'")
120
-                  if $tag_arg =~ /\?/;
121
-            }
122
-            
123
-            # Add tag to parsing tree
124
-            push @$tree, {type => 'tag', tag_name => $tag_name, tag_args => [@tag_args]};
125
-        }
126
-    }
127
-    
128
-    # Add text to parsing tree 
129
-    push @$tree, {type => 'text', tag_args => [$template]} if $template;
130
-    
131
-    return $tree;
132
-}
133
-
134
-sub _build_query {
135
-    my ($self, $tree) = @_;
136
-    
137
-    # SQL
138
-    my $sql = '';
139
-    
140
-    # All parameter key infomation
141
-    my $all_columns = [];
142
-    
143
-    # Build SQL 
144
-    foreach my $node (@$tree) {
145
-        
146
-        # Get type, tag name, and arguments
147
-        my $type     = $node->{type};
148
-        my $tag_name = $node->{tag_name};
149
-        my $tag_args = $node->{tag_args};
150
-        
151
-        # Text
152
-        if ($type eq 'text') {
153
-            # Join text
154
-            $sql .= $tag_args->[0];
155
-        }
156
-        
157
-        # Tag
158
-        elsif ($type eq 'tag') {
159
-            
160
-            # Get tag processor
161
-            my $tag_processor = $self->tag_processors->{$tag_name};
162
-            
163
-            # Tag processor is code ref?
164
-            croak("Tag processor '$tag_name' must be code reference")
165
-              unless ref $tag_processor eq 'CODE';
166
-            
167
-            # Expand tag using tag processor
168
-            my ($expand, $columns) = $tag_processor->($tag_name, $tag_args);
169
-            
170
-            # Check tag processor return value
171
-            croak("Tag processor '$tag_name' must return (\$expand, \$columns)")
172
-              if !defined $expand || ref $columns ne 'ARRAY';
173
-            
174
-            # Check placeholder count
175
-            croak("Placeholder count in SQL created by tag processor '$tag_name' " .
176
-                  "must be same as key informations count")
177
-              unless $self->_placeholder_count($expand) eq @$columns;
178
-            
179
-            # Add key information
180
-            push @$all_columns, @$columns;
181
-            
182
-            # Join expand tag to SQL
183
-            $sql .= $expand;
184
-        }
185
-    }
186
-    
187
-    # Add semicolon
188
-    $sql .= ';' unless $sql =~ /;$/;
189
-    
190
-    # Query
191
-    my $query = DBIx::Custom::Query->new(sql => $sql, columns => $all_columns);
192
-    
193
-    return $query;
194
-}
195
-
196
-sub _placeholder_count {
197
-    my ($self, $expand) = @_;
198
-    $expand ||= '';
199
-    
200
-    my $count = 0;
201
-    my $pos   = -1;
202
-    while (($pos = index($expand, '?', $pos + 1)) != -1) {
203
-        $count++;
204
-    }
205
-    return $count;
206
-}
207
-
208
-1;
209
-
210
-=head1 NAME
211
-
212
-DBIx::Custom::SQLTemplate - SQL template system
213
-
214
-=head1 SYNOPSIS
215
-    
216
-    my $sql_tmpl = DBIx::Custom::SQLTemplate->new;
217
-    
218
-    my $tmpl   = "select from table {= k1} && {<> k2} || {like k3}";
219
-    my $param = {k1 => 1, k2 => 2, k3 => 3};
220
-    
221
-    my $query = $sql_template->create_query($tmpl);
222
-
223
-=head1 ATTRIBUTES
224
-
225
-=head2 C<tag_processors>
226
-
227
-    $sql_tmpl       = $sql_tmpl->tag_processors($name1 => $tag_processor1
228
-                                                $name2 => $tag_processor2);
229
-    $tag_processors = $sql_tmpl->tag_processors;
230
-
231
-=head2 C<tag_start>
232
-    
233
-    $sql_tmpl  = $sql_tmpl->tag_start('{');
234
-    $tag_start = $sql_tmpl->tag_start;
235
-
236
-Default is '{'
237
-
238
-=head2 C<tag_end>
239
-    
240
-    $sql_tmpl    = $sql_tmpl->tag_start('}');
241
-    $tag_end = $sql_tmpl->tag_start;
242
-
243
-Default is '}'
244
-    
245
-=head2 C<tag_syntax>
246
-    
247
-    $sql_tmpl   = $sql_tmpl->tag_syntax($tag_syntax);
248
-    $tag_syntax = $sql_tmpl->tag_syntax;
249
-
250
-=head1 METHODS
251
-
252
-This class is L<Object::Simple> subclass.
253
-You can use all methods of L<Object::Simple>
254
-
255
-=head2 C<new>
256
-
257
-=head2 C<create_query>
258
-    
259
-Create L<DBIx::Custom::Query> object parsing SQL template
260
-
261
-    $query = $sql_tmpl->create_query($tmpl);
262
-    
263
-    # Sample
264
-    $query = $sql_tmpl->create_sql(
265
-         "select * from table where {= title} && {like author} || {<= price}")
266
-    
267
-    # Expanded
268
-    $qeury->sql : "select * from table where title = ? && author like ? price <= ?;"
269
-    $query->key_infos : [['title'], ['author'], ['price']]
270
-    
271
-    # Sample with table name
272
-    ($sql, @bind_values) = $sql_tmpl->create_sql(
273
-            "select * from table where {= table.title} && {like table.author}",
274
-            {table => {title => 'Perl', author => '%Taro%'}}
275
-        )
276
-    
277
-    # Expanded
278
-    $query->sql : "select * from table where table.title = ? && table.title like ?;"
279
-    $query->key_infos :[ [['table.title'],['table', 'title']],
280
-                         [['table.author'],['table', 'author']] ]
281
-
282
-This method create query using by L<DBIx::Custom>.
283
-query has two infomation
284
-
285
-    1. sql       : SQL
286
-    2. key_infos : Parameter access key information
287
-
288
-=head2 C<register_tag_processor>
289
-
290
-Add tag processor
291
-    
292
-    $sql_tmpl = $sql_tmpl->register_tag_processor($tag_processor);
293
-
294
-The following is register_tag_processor sample
295
-
296
-    $sql_tmpl->register_tag_processor(
297
-        '?' => sub {
298
-            my ($tag_name, $tag_args) = @_;
299
-            
300
-            my $key1 = $tag_args->[0];
301
-            my $key2 = $tag_args->[1];
302
-            
303
-            my $key_infos = [];
304
-            
305
-            # Expand tag and create key informations
306
-            
307
-            # Return expand tags and key informations
308
-            return ($expand, $key_infos);
309
-        }
310
-    );
311
-
312
-Tag processor recieve 2 argument
313
-
314
-    1. Tag name            (?, =, <>, or etc)
315
-    2. Tag arguments       (arg1 and arg2 in {tag_name arg1 arg2})
316
-
317
-Tag processor return 2 value
318
-
319
-    1. Expanded Tag (For exsample, '{= title}' is expanded to 'title = ?')
320
-    2. Key infomations
321
-    
322
-You must be return expanded tag and key infomations.
323
-
324
-Key information is a little complex. so I will explan this in future.
325
-
326
-If you want to know more, Please see DBIx::Custom::SQLTemplate source code.
327
-
328
-=head2 C<clone>
329
-
330
-Clone DBIx::Custom::SQLTemplate object
331
-
332
-    $clone = $sql_tmpl->clone;
333
-    
334
-=head1 Available Tags
335
-    
336
-Available Tags
337
-
338
-    [tag]            [expand]
339
-    {? name}         ?
340
-    {= name}         name = ?
341
-    {<> name}        name <> ?
342
-    
343
-    {< name}         name < ?
344
-    {> name}         name > ?
345
-    {>= name}        name >= ?
346
-    {<= name}        name <= ?
347
-    
348
-    {like name}      name like ?
349
-    {in name}        name in [?, ?, ..]
350
-    
351
-    {insert}         (key1, key2, key3) values (?, ?, ?)
352
-    {update}         set key1 = ?, key2 = ?, key3 = ?
353
-    
354
-
355
-The following is insert SQL sample
356
-
357
-    $query = $sql_tmpl->create_sql(
358
-        "insert into table {insert key1 key2}"
359
-    );
360
-    
361
-    # Expanded
362
-    $query->sql : "insert into table (key1, key2) values (?, ?)"
363
-
364
-The following is update SQL sample
365
-    
366
-    $query = $sql_tmpl->create_sql(
367
-        "update table {update key1 key2} where {= key3}"
368
-    );
369
-    
370
-    # Expanded
371
-    $query->sql : "update table set key1 = ?, key2 = ? where key3 = ?;"
372
-    
373
-=cut
374
-
+13 -13
t/dbix-custom-core-sqlite.t
... ...
@@ -70,7 +70,7 @@ $dbi->insert(table => 'table1', param => {key1 => 3, key2 => 4});
70 70
 
71 71
 test 'DBIx::Custom::Result test';
72 72
 $tmpl = "select key1, key2 from table1";
73
-$query = $dbi->create_query($tmpl);
73
+$query = $dbi->build_query($tmpl);
74 74
 $result = $dbi->execute($query);
75 75
 
76 76
 @rows = ();
... ...
@@ -98,7 +98,7 @@ test 'Insert query return value';
98 98
 $dbi->execute($DROP_TABLE->{0});
99 99
 $dbi->execute($CREATE_TABLE->{0});
100 100
 $tmpl = "insert into table1 {insert key1 key2}";
101
-$query = $dbi->create_query($tmpl);
101
+$query = $dbi->build_query($tmpl);
102 102
 $ret_val = $dbi->execute($query, param => {key1 => 1, key2 => 2});
103 103
 ok($ret_val, $test);
104 104
 
... ...
@@ -119,7 +119,7 @@ $dbi->register_filter(twice       => sub { $_[0] * 2},
119 119
                     three_times => sub { $_[0] * 3});
120 120
 
121 121
 $insert_tmpl  = "insert into table1 {insert key1 key2};";
122
-$insert_query = $dbi->create_query($insert_tmpl);
122
+$insert_query = $dbi->build_query($insert_tmpl);
123 123
 $insert_query->filter({key1 => 'twice'});
124 124
 $dbi->execute($insert_query, param => {key1 => 1, key2 => 2});
125 125
 $result = $dbi->execute($SELECT_TMPLS->{0});
... ...
@@ -130,10 +130,10 @@ $dbi->execute($DROP_TABLE->{0});
130 130
 test 'Filter in';
131 131
 $dbi->execute($CREATE_TABLE->{0});
132 132
 $insert_tmpl  = "insert into table1 {insert key1 key2};";
133
-$insert_query = $dbi->create_query($insert_tmpl);
133
+$insert_query = $dbi->build_query($insert_tmpl);
134 134
 $dbi->execute($insert_query, param => {key1 => 2, key2 => 4});
135 135
 $select_tmpl = "select * from table1 where {in table1.key1 2} and {in table1.key2 2}";
136
-$select_query = $dbi->create_query($select_tmpl);
136
+$select_query = $dbi->build_query($select_tmpl);
137 137
 $select_query->filter({'table1.key1' => 'twice'});
138 138
 $result = $dbi->execute($select_query, param => {'table1.key1' => [1,5], 'table1.key2' => [2,4]});
139 139
 $rows = $result->fetch_hash_all;
... ...
@@ -146,13 +146,13 @@ $dbi->insert(table => 'table1', param => {key1 => 1, key2 => 2, key3 => 3, key4
146 146
 $dbi->insert(table => 'table1', param => {key1 => 6, key2 => 7, key3 => 8, key4 => 9, key5 => 10});
147 147
 
148 148
 $tmpl = "select * from table1 where {= key1} and {<> key2} and {< key3} and {> key4} and {>= key5};";
149
-$query = $dbi->create_query($tmpl);
149
+$query = $dbi->build_query($tmpl);
150 150
 $result = $dbi->execute($query, param => {key1 => 1, key2 => 3, key3 => 4, key4 => 3, key5 => 5});
151 151
 $rows = $result->fetch_hash_all;
152 152
 is_deeply($rows, [{key1 => 1, key2 => 2, key3 => 3, key4 => 4, key5 => 5}], "$test : basic tag1");
153 153
 
154 154
 $tmpl = "select * from table1 where {<= key1} and {like key2};";
155
-$query = $dbi->create_query($tmpl);
155
+$query = $dbi->build_query($tmpl);
156 156
 $result = $dbi->execute($query, param => {key1 => 1, key2 => '%2%'});
157 157
 $rows = $result->fetch_hash_all;
158 158
 is_deeply($rows, [{key1 => 1, key2 => 2, key3 => 3, key4 => 4, key5 => 5}], "$test : basic tag2");
... ...
@@ -164,7 +164,7 @@ $dbi->insert(table => 'table1', param => {key1 => 1, key2 => 2, key3 => 3, key4
164 164
 $dbi->insert(table => 'table1', param => {key1 => 6, key2 => 7, key3 => 8, key4 => 9, key5 => 10});
165 165
 
166 166
 $tmpl = "select * from table1 where {in key1 2};";
167
-$query = $dbi->create_query($tmpl);
167
+$query = $dbi->build_query($tmpl);
168 168
 $result = $dbi->execute($query, param => {key1 => [9, 1]});
169 169
 $rows = $result->fetch_hash_all;
170 170
 is_deeply($rows, [{key1 => 1, key2 => 2, key3 => 3, key4 => 4, key5 => 5}], "$test : basic");
... ...
@@ -197,8 +197,8 @@ eval {DBIx::Custom->connect(data_source => 'dbi:SQLit')};
197 197
 ok($@, "$test : connect error");
198 198
 
199 199
 $dbi = DBIx::Custom->connect($NEW_ARGS->{0});
200
-eval{$dbi->create_query("{p }")};
201
-ok($@, "$test : create_query invalid SQL template");
200
+eval{$dbi->build_query("{p }")};
201
+ok($@, "$test : build_query invalid SQL template");
202 202
 
203 203
 test 'insert';
204 204
 $dbi = DBIx::Custom->connect($NEW_ARGS->{0});
... ...
@@ -214,12 +214,12 @@ $dbi->register_filter(
214 214
     twice       => sub { $_[0] * 2 },
215 215
     three_times => sub { $_[0] * 3 }
216 216
 );
217
-$dbi->default_query_filter('twice');
217
+$dbi->default_bind_filter('twice');
218 218
 $dbi->insert(table => 'table1', param => {key1 => 1, key2 => 2}, filter => {key1 => 'three_times'});
219 219
 $result = $dbi->execute($SELECT_TMPLS->{0});
220 220
 $rows   = $result->fetch_hash_all;
221 221
 is_deeply($rows, [{key1 => 3, key2 => 4}], "$test : filter");
222
-$dbi->default_query_filter(undef);
222
+$dbi->default_bind_filter(undef);
223 223
 
224 224
 $dbi->execute($DROP_TABLE->{0});
225 225
 $dbi->execute($CREATE_TABLE->{0});
... ...
@@ -414,7 +414,7 @@ test 'cache';
414 414
 $dbi = DBIx::Custom->connect($NEW_ARGS->{0});
415 415
 $dbi->execute($CREATE_TABLE->{0});
416 416
 $tmpl = 'select * from table1 where {= key1} and {= key2};';
417
-$dbi->create_query($tmpl);
417
+$dbi->build_query($tmpl);
418 418
 is_deeply($dbi->{_cached}->{$tmpl}, 
419 419
           {sql => "select * from table1 where key1 = ? and key2 = ?;", columns => ['key1', 'key2']}, "$test : cache");
420 420
 
+2 -2
t/dbix-custom-core.t
... ...
@@ -31,12 +31,12 @@ $dbi = DBIx::Custom->new(
31 31
     default_bind_filter => 'f',
32 32
     default_fetch_filter => 'g',
33 33
     result_class => 'g',
34
-    sql_template => $SQL_TMPL->{0},
34
+    sql_builder_class => $SQL_TMPL->{0},
35 35
 );
36 36
 is_deeply($dbi,{user => 'a', database => 'a', password => 'b', data_source => 'c', 
37 37
                 filters => {f => 3}, default_bind_filter => 'f',
38 38
                 default_fetch_filter => 'g', result_class => 'g',
39
-                sql_template => $SQL_TMPL->{0}}, $test);
39
+                sql_builder_class => $SQL_TMPL->{0}}, $test);
40 40
 isa_ok($dbi, 'DBIx::Custom');
41 41
 
42 42