DBIx-Custom / lib / DBIx / Custom / SQLTemplate.pm /
Newer Older
509 lines | 13.483kb
many many changes
yuki-kimoto authored on 2010-04-30
1
package DBIx::Custom::SQLTemplate;
2

            
3
use strict;
4
use warnings;
5

            
6
use base 'Object::Simple';
7
use Carp 'croak';
8
use DBIx::Custom::Query;
9

            
10
__PACKAGE__->dual_attr('tag_processors', default => sub { {} },
11
                                         inherit => 'hash_copy');
12

            
13
__PACKAGE__->dual_attr('tag_start', default => '{', inherit => 'scalar_copy');
14
__PACKAGE__->dual_attr('tag_end',   default => '}', inherit => 'scalar_copy');
15

            
16
__PACKAGE__->dual_attr('tag_syntax', inherit => 'scalar_copy');
17

            
18
__PACKAGE__->add_tag_processor(
19
    '?'      => \&DBIx::Custom::SQLTemplate::TagProcessors::expand_basic_tag,
20
    '='      => \&DBIx::Custom::SQLTemplate::TagProcessors::expand_basic_tag,
21
    '<>'     => \&DBIx::Custom::SQLTemplate::TagProcessors::expand_basic_tag,
22
    '>'      => \&DBIx::Custom::SQLTemplate::TagProcessors::expand_basic_tag,
23
    '<'      => \&DBIx::Custom::SQLTemplate::TagProcessors::expand_basic_tag,
24
    '>='     => \&DBIx::Custom::SQLTemplate::TagProcessors::expand_basic_tag,
25
    '<='     => \&DBIx::Custom::SQLTemplate::TagProcessors::expand_basic_tag,
26
    'like'   => \&DBIx::Custom::SQLTemplate::TagProcessors::expand_basic_tag,
27
    'in'     => \&DBIx::Custom::SQLTemplate::TagProcessors::expand_in_tag,
28
    'insert' => \&DBIx::Custom::SQLTemplate::TagProcessors::expand_insert_tag,
29
    'update' => \&DBIx::Custom::SQLTemplate::TagProcessors::expand_update_tag
30
);
31

            
32
__PACKAGE__->tag_syntax(<< 'EOS');
33
[tag]                     [expand]
34
{? name}                  ?
35
{= name}                  name = ?
36
{<> name}                 name <> ?
37

            
38
{< name}                  name < ?
39
{> name}                  name > ?
40
{>= name}                 name >= ?
41
{<= name}                 name <= ?
42

            
43
{like name}               name like ?
44
{in name number}          name in [?, ?, ..]
45

            
46
{insert key1 key2} (key1, key2) values (?, ?)
47
{update key1 key2}    set key1 = ?, key2 = ?
48
EOS
49

            
50

            
51
sub add_tag_processor {
52
    my $invocant = shift;
53
    my $tag_processors = ref $_[0] eq 'HASH' ? $_[0] : {@_};
54
    $invocant->tag_processors({%{$invocant->tag_processors}, %{$tag_processors}});
55
    return $invocant;
56
}
57

            
58
sub clone {
59
    my $self = shift;
60
    my $new = $self->new;
61
    
62
    $new->tag_start($self->tag_start);
63
    $new->tag_end($self->tag_end);
64
    $new->tag_syntax($self->tag_syntax);
65
    $new->tag_processors({%{$self->tag_processors || {}}});
66
    
67
    return $new;
68
}
69

            
70
sub create_query {
71
    my ($self, $template)  = @_;
72
    
73
    # Parse template
74
    my $tree = $self->_parse_template($template);
75
    
76
    # Build query
77
    my $query = $self->_build_query($tree);
78
    
79
    return $query;
80
}
81

            
82
sub _parse_template {
83
    my ($self, $template) = @_;
84
    
85
    if (ref $template eq 'ARRAY') {
86
        $template = $template->[1];
87
    }
88
    $template ||= '';
89
    
90
    my $tree = [];
91
    
92
    # Tags
93
    my $tag_start = quotemeta $self->tag_start;
94
    my $tag_end   = quotemeta $self->tag_end;
95
    
96
    # Tokenize
97
    my $state = 'text';
98
    
99
    # Save original template
100
    my $original_template = $template;
101
    
102
    # Parse template
103
    while ($template =~ s/([^$tag_start]*?)$tag_start([^$tag_end].*?)$tag_end//sm) {
104
        my $text = $1;
105
        my $tag  = $2;
106
        
107
        # Parse tree
108
        push @$tree, {type => 'text', tag_args => [$text]} if $text;
109
        
110
        if ($tag) {
111
            # Get tag name and arguments
112
            my ($tag_name, @tag_args) = split /\s+/, $tag;
113
            
114
            # Tag processor is exist?
115
            unless ($self->tag_processors->{$tag_name}) {
116
                my $tag_syntax = $self->tag_syntax;
117
                croak("Tag '{$tag}' in SQL template is not exist.\n\n" .
118
                      "<SQL template tag syntax>\n" .
119
                      "$tag_syntax\n" .
120
                      "<Your SQL template>\n" .
121
                      "$original_template\n\n");
122
            }
123
            
124
            # Check tag arguments
125
            foreach my $tag_arg (@tag_args) {
126
                # Cannot cantain placehosder '?'
127
                croak("Tag '{t }' arguments cannot contain '?'")
128
                  if $tag_arg =~ /\?/;
129
            }
130
            
131
            # Add tag to parsing tree
132
            push @$tree, {type => 'tag', tag_name => $tag_name, tag_args => [@tag_args]};
133
        }
134
    }
135
    
136
    # Add text to parsing tree 
137
    push @$tree, {type => 'text', tag_args => [$template]} if $template;
138
    
139
    return $tree;
140
}
141

            
142
sub _build_query {
143
    my ($self, $tree) = @_;
144
    
145
    # SQL
146
    my $sql = '';
147
    
148
    # All parameter key infomation
149
    my $all_key_infos = [];
150
    
151
    # Build SQL 
152
    foreach my $node (@$tree) {
153
        
154
        # Get type, tag name, and arguments
155
        my $type     = $node->{type};
156
        my $tag_name = $node->{tag_name};
157
        my $tag_args = $node->{tag_args};
158
        
159
        # Text
160
        if ($type eq 'text') {
161
            # Join text
162
            $sql .= $tag_args->[0];
163
        }
164
        
165
        # Tag
166
        elsif ($type eq 'tag') {
167
            
168
            # Get tag processor
169
            my $tag_processor = $self->tag_processors->{$tag_name};
170
            
171
            # Tag processor is code ref?
172
            croak("Tag processor '$tag_name' must be code reference")
173
              unless ref $tag_processor eq 'CODE';
174
            
175
            # Expand tag using tag processor
176
            my ($expand, $key_infos)
177
              = $tag_processor->($tag_name, $tag_args);
178
            
179
            # Check tag processor return value
180
            croak("Tag processor '$tag_name' must return (\$expand, \$key_infos)")
181
              if !defined $expand || ref $key_infos ne 'ARRAY';
182
            
183
            # Check placeholder count
184
            croak("Placeholder count in SQL created by tag processor '$tag_name' " .
185
                  "must be same as key informations count")
186
              unless $self->_placeholder_count($expand) eq @$key_infos;
187
            
188
            # Add key information
189
            push @$all_key_infos, @$key_infos;
190
            
191
            # Join expand tag to SQL
192
            $sql .= $expand;
193
        }
194
    }
195
    
196
    # Add semicolon
197
    $sql .= ';' unless $sql =~ /;$/;
198
    
199
    # Query
200
    my $query = DBIx::Custom::Query->new(sql => $sql, key_infos => $all_key_infos);
201
    
202
    return $query;
203
}
204

            
205
sub _placeholder_count {
206
    my ($self, $expand) = @_;
207
    $expand ||= '';
208
    
209
    my $count = 0;
210
    my $pos   = -1;
211
    while (($pos = index($expand, '?', $pos + 1)) != -1) {
212
        $count++;
213
    }
214
    return $count;
215
}
216

            
217
1;
218

            
219
package DBIx::Custom::SQLTemplate::TagProcessors;
220

            
221
use strict;
222
use warnings;
223

            
224
use Carp 'croak';
225
use DBIx::Custom::KeyInfo;
226

            
227
sub expand_basic_tag {
228
    my ($tag_name, $tag_args) = @_;
229
    
230
    # Key
231
    my $column = $tag_args->[0];
232
    
233
    # Key is not exist
234
    croak("You must be pass key as argument to tag '{$tag_name }'")
235
      unless $column;
many change
yuki-kimoto authored on 2010-04-30
236
    
237
    # delete ID
238
    
239
    
many many changes
yuki-kimoto authored on 2010-04-30
240
    # Expanded tag
241
    my $expand = $tag_name eq '?'
242
               ? '?'
243
               : "$column $tag_name ?";
244

            
245
    return ($expand, [{column => $column}]);
246
}
247

            
248
sub expand_in_tag {
249
    my ($tag_name, $tag_args) = @_;
250
    my ($column, $placeholder_count) = @$tag_args;
251
    
252
    # Key must be specified
253
    croak("You must be pass key as first argument of tag '{$tag_name }'\n" . 
254
          "Usage: {$tag_name \$key \$placeholder_count}")
255
      unless $column;
256
    
257
    # Place holder count must be specified
258
    croak("You must be pass placeholder count as second argument of tag '{$tag_name }'\n" . 
259
          "Usage: {$tag_name \$key \$placeholder_count}")
260
      if !$placeholder_count || $placeholder_count =~ /\D/;
261

            
262
    # Expand tag
263
    my $expand = "$column $tag_name (";
264
    for (my $i = 0; $i < $placeholder_count; $i++) {
265
        $expand .= '?, ';
266
    }
267
    
268
    $expand =~ s/, $//;
269
    $expand .= ')';
270
    
271
    # Create parameter key infomations
272
    my $key_infos = [];
273
    for (my $i = 0; $i < $placeholder_count; $i++) {
274
        
275
        # Add parameter key infos
276
        push @$key_infos, {column => $column, pos => $i};
277
    }
278
    
279
    return ($expand, $key_infos);
280
}
281

            
282
sub expand_insert_tag {
283
    my ($tag_name, $columns) = @_;
284
    
285
    # Insert key (k1, k2, k3, ..)
286
    my $insert_keys = '(';
287
    
288
    # placeholder (?, ?, ?, ..)
289
    my $place_holders = '(';
290
    
291
    foreach my $column (@$columns) {
292
        
293
        # Join insert column
294
        $insert_keys   .= "$column, ";
295
        
296
        # Join place holder
297
        $place_holders .= "?, ";
298
    }
299
    
300
    # Delete last ', '
301
    $insert_keys =~ s/, $//;
302
    
303
    # Close 
304
    $insert_keys .= ')';
305
    $place_holders =~ s/, $//;
306
    $place_holders .= ')';
307
    
308
    # Expand tag
309
    my $expand = "$insert_keys values $place_holders";
310
    
311
    # Create parameter key infomations
312
    my $key_infos = [];
313
    foreach my $column (@$columns) {
314
        push @$key_infos, {column => $column};
315
    }
316
    
317
    return ($expand, $key_infos);
318
}
319

            
320
sub expand_update_tag {
321
    my ($tag_name, $columns) = @_;
322
    
323
    # Expanded tag
324
    my $expand = 'set ';
325
    
326
    foreach my $column (@$columns) {
327

            
328
        # Join key and placeholder
329
        $expand .= "$column = ?, ";
330
    }
331
    
332
    # Delete last ', '
333
    $expand =~ s/, $//;
334
    
335
    my $key_infos = [];
336
    foreach my $column (@$columns) {
337
        push @$key_infos, {column => $column};
338
    }
339
    
340
    return ($expand, $key_infos);
341
}
342

            
343
package DBIx::Custom::SQLTemplate;
344

            
345
1;
346

            
347
=head1 NAME
348

            
349
DBIx::Custom::SQLTemplate - DBIx::Custom SQL Template
350

            
351
=head1 SYNOPSIS
352
    
353
    my $sql_tmpl = DBIx::Custom::SQLTemplate->new;
354
    
355
    my $tmpl   = "select from table {= k1} && {<> k2} || {like k3}";
356
    my $param = {k1 => 1, k2 => 2, k3 => 3};
357
    
358
    my $query = $sql_template->create_query($tmpl);
359

            
360
=head1 ATTRIBUTES
361

            
362
=head2 tag_processors
363

            
364
    $sql_tmpl       = $sql_tmpl->tag_processors($name1 => $tag_processor1
365
                                                $name2 => $tag_processor2);
366
    $tag_processors = $sql_tmpl->tag_processors;
367

            
368
=head2 tag_start
369
    
370
    $sql_tmpl  = $sql_tmpl->tag_start('{');
371
    $tag_start = $sql_tmpl->tag_start;
372

            
373
Default is '{'
374

            
375
=head2 tag_end
376
    
377
    $sql_tmpl    = $sql_tmpl->tag_start('}');
378
    $tag_end = $sql_tmpl->tag_start;
379

            
380
Default is '}'
381
    
382
=head2 tag_syntax
383
    
384
    $sql_tmpl   = $sql_tmpl->tag_syntax($tag_syntax);
385
    $tag_syntax = $sql_tmpl->tag_syntax;
386

            
387
=head1 METHODS
388

            
389
This class is L<Object::Simple> subclass.
390
You can use all methods of L<Object::Simple>
391

            
392
=head2 create_query
393
    
394
Create L<DBIx::Custom::Query> object parsing SQL template
395

            
396
    $query = $sql_tmpl->create_query($tmpl);
397
    
398
    # Sample
399
    $query = $sql_tmpl->create_sql(
400
         "select * from table where {= title} && {like author} || {<= price}")
401
    
402
    # Expanded
403
    $qeury->sql : "select * from table where title = ? && author like ? price <= ?;"
404
    $query->key_infos : [['title'], ['author'], ['price']]
405
    
406
    # Sample with table name
407
    ($sql, @bind_values) = $sql_tmpl->create_sql(
408
            "select * from table where {= table.title} && {like table.author}",
409
            {table => {title => 'Perl', author => '%Taro%'}}
410
        )
411
    
412
    # Expanded
413
    $query->sql : "select * from table where table.title = ? && table.title like ?;"
414
    $query->key_infos :[ [['table.title'],['table', 'title']],
415
                         [['table.author'],['table', 'author']] ]
416

            
417
This method create query using by L<DBIx::Custom>.
418
query has two infomation
419

            
420
    1. sql       : SQL
421
    2. key_infos : Parameter access key information
422

            
423
=head2 add_tag_processor
424

            
425
Add tag processor
426
    
427
    $sql_tmpl = $sql_tmpl->add_tag_processor($tag_processor);
428

            
429
The following is add_tag_processor sample
430

            
431
    $sql_tmpl->add_tag_processor(
432
        '?' => sub {
433
            my ($tag_name, $tag_args) = @_;
434
            
435
            my $key1 = $tag_args->[0];
436
            my $key2 = $tag_args->[1];
437
            
438
            my $key_infos = [];
439
            
440
            # Expand tag and create key informations
441
            
442
            # Return expand tags and key informations
443
            return ($expand, $key_infos);
444
        }
445
    );
446

            
447
Tag processor recieve 2 argument
448

            
449
    1. Tag name            (?, =, <>, or etc)
450
    2. Tag arguments       (arg1 and arg2 in {tag_name arg1 arg2})
451

            
452
Tag processor return 2 value
453

            
454
    1. Expanded Tag (For exsample, '{= title}' is expanded to 'title = ?')
455
    2. Key infomations
456
    
457
You must be return expanded tag and key infomations.
458

            
459
Key information is a little complex. so I will explan this in future.
460

            
461
If you want to know more, Please see DBIx::Custom::SQLTemplate source code.
462

            
463
=head2 clone
464

            
465
Clone DBIx::Custom::SQLTemplate object
466

            
467
    $clone = $sql_tmpl->clone;
468
    
469
=head1 Available Tags
470
    
471
Available Tags
472

            
473
    [tag]            [expand]
474
    {? name}         ?
475
    {= name}         name = ?
476
    {<> name}        name <> ?
477
    
478
    {< name}         name < ?
479
    {> name}         name > ?
480
    {>= name}        name >= ?
481
    {<= name}        name <= ?
482
    
483
    {like name}      name like ?
484
    {in name}        name in [?, ?, ..]
485
    
486
    {insert}         (key1, key2, key3) values (?, ?, ?)
487
    {update}         set key1 = ?, key2 = ?, key3 = ?
488
    
489

            
490
The following is insert SQL sample
491

            
492
    $query = $sql_tmpl->create_sql(
493
        "insert into table {insert key1 key2}"
494
    );
495
    
496
    # Expanded
497
    $query->sql : "insert into table (key1, key2) values (?, ?)"
498

            
499
The following is update SQL sample
500
    
501
    $query = $sql_tmpl->create_sql(
502
        "update table {update key1 key2} where {= key3}"
503
    );
504
    
505
    # Expanded
506
    $query->sql : "update table set key1 = ?, key2 = ? where key3 = ?;"
507
    
508
=cut
509