DBIx-Custom / lib / DBIx / Custom / SQLTemplate.pm /
Newer Older
386 lines | 10.565kb
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';
simplify filtering system
yuki-kimoto authored on 2010-05-01
7

            
many many changes
yuki-kimoto authored on 2010-04-30
8
use Carp 'croak';
9
use DBIx::Custom::Query;
compile success
yuki-kimoto authored on 2010-05-01
10
use DBIx::Custom::SQLTemplate::TagProcessor;
many many changes
yuki-kimoto authored on 2010-04-30
11

            
add query filter error check
yuki-kimoto authored on 2010-05-14
12
__PACKAGE__->attr('tag_processors' => sub { {} });
13

            
14
__PACKAGE__->attr(tag_start => '{');
15
__PACKAGE__->attr(tag_end   => '}');
16

            
17
__PACKAGE__->attr('tag_syntax' => <<'EOS');
many many changes
yuki-kimoto authored on 2010-04-30
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

            
add query filter error check
yuki-kimoto authored on 2010-05-14
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
}
many many changes
yuki-kimoto authored on 2010-04-30
54

            
some changed
yuki-kimoto authored on 2010-05-02
55
sub register_tag_processor {
add query filter error check
yuki-kimoto authored on 2010-05-14
56
    my $self = shift;
many many changes
yuki-kimoto authored on 2010-04-30
57
    my $tag_processors = ref $_[0] eq 'HASH' ? $_[0] : {@_};
add query filter error check
yuki-kimoto authored on 2010-05-14
58
    $self->tag_processors({%{$self->tag_processors}, %{$tag_processors}});
59
    return $self;
many many changes
yuki-kimoto authored on 2010-04-30
60
}
61

            
62
sub clone {
63
    my $self = shift;
64
    my $new = $self->new;
65
    
66
    $new->tag_start($self->tag_start);
67
    $new->tag_end($self->tag_end);
68
    $new->tag_syntax($self->tag_syntax);
69
    $new->tag_processors({%{$self->tag_processors || {}}});
70
    
71
    return $new;
72
}
73

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

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

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

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

            
220
1;
221

            
222
=head1 NAME
223

            
224
DBIx::Custom::SQLTemplate - DBIx::Custom SQL Template
225

            
226
=head1 SYNOPSIS
227
    
228
    my $sql_tmpl = DBIx::Custom::SQLTemplate->new;
229
    
230
    my $tmpl   = "select from table {= k1} && {<> k2} || {like k3}";
231
    my $param = {k1 => 1, k2 => 2, k3 => 3};
232
    
233
    my $query = $sql_template->create_query($tmpl);
234

            
235
=head1 ATTRIBUTES
236

            
237
=head2 tag_processors
238

            
239
    $sql_tmpl       = $sql_tmpl->tag_processors($name1 => $tag_processor1
240
                                                $name2 => $tag_processor2);
241
    $tag_processors = $sql_tmpl->tag_processors;
242

            
243
=head2 tag_start
244
    
245
    $sql_tmpl  = $sql_tmpl->tag_start('{');
246
    $tag_start = $sql_tmpl->tag_start;
247

            
248
Default is '{'
249

            
250
=head2 tag_end
251
    
252
    $sql_tmpl    = $sql_tmpl->tag_start('}');
253
    $tag_end = $sql_tmpl->tag_start;
254

            
255
Default is '}'
256
    
257
=head2 tag_syntax
258
    
259
    $sql_tmpl   = $sql_tmpl->tag_syntax($tag_syntax);
260
    $tag_syntax = $sql_tmpl->tag_syntax;
261

            
262
=head1 METHODS
263

            
264
This class is L<Object::Simple> subclass.
265
You can use all methods of L<Object::Simple>
266

            
add query filter error check
yuki-kimoto authored on 2010-05-14
267
=head2 new
268

            
many many changes
yuki-kimoto authored on 2010-04-30
269
=head2 create_query
270
    
271
Create L<DBIx::Custom::Query> object parsing SQL template
272

            
273
    $query = $sql_tmpl->create_query($tmpl);
274
    
275
    # Sample
276
    $query = $sql_tmpl->create_sql(
277
         "select * from table where {= title} && {like author} || {<= price}")
278
    
279
    # Expanded
280
    $qeury->sql : "select * from table where title = ? && author like ? price <= ?;"
281
    $query->key_infos : [['title'], ['author'], ['price']]
282
    
283
    # Sample with table name
284
    ($sql, @bind_values) = $sql_tmpl->create_sql(
285
            "select * from table where {= table.title} && {like table.author}",
286
            {table => {title => 'Perl', author => '%Taro%'}}
287
        )
288
    
289
    # Expanded
290
    $query->sql : "select * from table where table.title = ? && table.title like ?;"
291
    $query->key_infos :[ [['table.title'],['table', 'title']],
292
                         [['table.author'],['table', 'author']] ]
293

            
294
This method create query using by L<DBIx::Custom>.
295
query has two infomation
296

            
297
    1. sql       : SQL
298
    2. key_infos : Parameter access key information
299

            
some changed
yuki-kimoto authored on 2010-05-02
300
=head2 register_tag_processor
many many changes
yuki-kimoto authored on 2010-04-30
301

            
302
Add tag processor
303
    
some changed
yuki-kimoto authored on 2010-05-02
304
    $sql_tmpl = $sql_tmpl->register_tag_processor($tag_processor);
many many changes
yuki-kimoto authored on 2010-04-30
305

            
some changed
yuki-kimoto authored on 2010-05-02
306
The following is register_tag_processor sample
many many changes
yuki-kimoto authored on 2010-04-30
307

            
some changed
yuki-kimoto authored on 2010-05-02
308
    $sql_tmpl->register_tag_processor(
many many changes
yuki-kimoto authored on 2010-04-30
309
        '?' => sub {
310
            my ($tag_name, $tag_args) = @_;
311
            
312
            my $key1 = $tag_args->[0];
313
            my $key2 = $tag_args->[1];
314
            
315
            my $key_infos = [];
316
            
317
            # Expand tag and create key informations
318
            
319
            # Return expand tags and key informations
320
            return ($expand, $key_infos);
321
        }
322
    );
323

            
324
Tag processor recieve 2 argument
325

            
326
    1. Tag name            (?, =, <>, or etc)
327
    2. Tag arguments       (arg1 and arg2 in {tag_name arg1 arg2})
328

            
329
Tag processor return 2 value
330

            
331
    1. Expanded Tag (For exsample, '{= title}' is expanded to 'title = ?')
332
    2. Key infomations
333
    
334
You must be return expanded tag and key infomations.
335

            
336
Key information is a little complex. so I will explan this in future.
337

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

            
340
=head2 clone
341

            
342
Clone DBIx::Custom::SQLTemplate object
343

            
344
    $clone = $sql_tmpl->clone;
345
    
346
=head1 Available Tags
347
    
348
Available Tags
349

            
350
    [tag]            [expand]
351
    {? name}         ?
352
    {= name}         name = ?
353
    {<> name}        name <> ?
354
    
355
    {< name}         name < ?
356
    {> name}         name > ?
357
    {>= name}        name >= ?
358
    {<= name}        name <= ?
359
    
360
    {like name}      name like ?
361
    {in name}        name in [?, ?, ..]
362
    
363
    {insert}         (key1, key2, key3) values (?, ?, ?)
364
    {update}         set key1 = ?, key2 = ?, key3 = ?
365
    
366

            
367
The following is insert SQL sample
368

            
369
    $query = $sql_tmpl->create_sql(
370
        "insert into table {insert key1 key2}"
371
    );
372
    
373
    # Expanded
374
    $query->sql : "insert into table (key1, key2) values (?, ?)"
375

            
376
The following is update SQL sample
377
    
378
    $query = $sql_tmpl->create_sql(
379
        "update table {update key1 key2} where {= key3}"
380
    );
381
    
382
    # Expanded
383
    $query->sql : "update table set key1 = ?, key2 = ? where key3 = ?;"
384
    
385
=cut
386