DBIx-Custom / lib / DBI / Custom.pm /
Newer Older
390 lines | 9.526kb
first commit
yuki-kimoto authored on 2009-10-13
1
package DBI::Custom;
2
use Object::Simple;
add test
yuki-kimoto authored on 2009-10-16
3

            
4
our $VERSION = '0.0101';
5

            
6
use Carp 'croak';
add some method
yuki-kimoto authored on 2009-10-14
7
use DBI;
first commit
yuki-kimoto authored on 2009-10-13
8

            
cleanup
yuki-kimoto authored on 2009-10-15
9
# Model
10
sub model : ClassAttr { auto_build => \&_inherit_model }
first commit
yuki-kimoto authored on 2009-10-13
11

            
cleanup
yuki-kimoto authored on 2009-10-15
12
# Inherit super class model
13
sub _inherit_model {
add test
yuki-kimoto authored on 2009-10-16
14
    my $class = shift;
cleanup
yuki-kimoto authored on 2009-10-15
15
    my $super = do {
16
        no strict 'refs';
17
        ${"${class}::ISA"}[0];
18
    };
19
    my $model = eval{$super->can('model')}
20
                         ? $super->model->clone
21
                         : $class->Object::Simple::new;
cleanup
yuki-kimoto authored on 2009-10-14
22
    
23
    $class->model($model);
first commit
yuki-kimoto authored on 2009-10-13
24
}
25

            
cleanup
yuki-kimoto authored on 2009-10-15
26
# New
27
sub new {
28
    my $self = shift->Object::Simple::new(@_);
29
    my $class = ref $self;
30
    return bless {%{$class->model->clone}, %{$self}}, $class;
first commit
yuki-kimoto authored on 2009-10-13
31
}
32

            
cleanup
yuki-kimoto authored on 2009-10-15
33
# Initialize modle
34
sub initialize_model {
35
    my ($class, $callback) = @_;
first commit
yuki-kimoto authored on 2009-10-13
36
    
cleanup
yuki-kimoto authored on 2009-10-15
37
    # Callback to initialize model
38
    $callback->($class->model);
first commit
yuki-kimoto authored on 2009-10-13
39
}
40

            
cleanup
yuki-kimoto authored on 2009-10-15
41
# Clone
42
sub clone {
cleanup
yuki-kimoto authored on 2009-10-14
43
    my $self = shift;
cleanup
yuki-kimoto authored on 2009-10-15
44
    my $new = $self->Object::Simple::new;
add test
yuki-kimoto authored on 2009-10-16
45
    $new->connect_info(%{$self->connect_info || {}});
cleanup
yuki-kimoto authored on 2009-10-15
46
    $new->filters(%{$self->filters || {}});
add test
yuki-kimoto authored on 2009-10-16
47
    $new->bind_filter($self->bind_filter);
48
    $new->fetch_filter($self->fetch_filter);
cleanup
yuki-kimoto authored on 2009-10-14
49
}
50

            
cleanup
yuki-kimoto authored on 2009-10-15
51
# Attribute
52
sub connect_info       : Attr { type => 'hash',  auto_build => sub { shift->connect_info({}) } }
cleanup
yuki-kimoto authored on 2009-10-15
53

            
add test
yuki-kimoto authored on 2009-10-16
54
sub bind_filter : Attr {}
55
sub fetch_filter : Attr {}
cleanup
yuki-kimoto authored on 2009-10-15
56

            
add test
yuki-kimoto authored on 2009-10-16
57
sub filters : Attr { type => 'hash', deref => 1, auto_build => sub { shift->filters({}) } }
cleanup
yuki-kimoto authored on 2009-10-15
58
sub add_filter { shift->filters(@_) }
59

            
cleanup
yuki-kimoto authored on 2009-10-15
60
sub dbh          : Attr { auto_build => sub { shift->connect } }
add test
yuki-kimoto authored on 2009-10-17
61
sub sql_template : Attr { auto_build => sub { shift->sql_template(DBI::Custom::SQLTemplate->new) } }
add test
yuki-kimoto authored on 2009-10-16
62

            
63
our %VALID_CONNECT_INFO = map {$_ => 1} qw/data_source user password options/;
cleanup
yuki-kimoto authored on 2009-10-14
64

            
add some method
yuki-kimoto authored on 2009-10-14
65
sub connect {
66
    my $self = shift;
67
    my $connect_info = $self->connect_info;
68
    
add test
yuki-kimoto authored on 2009-10-16
69
    foreach my $key (keys %{$self->connect_info}) {
add test
yuki-kimoto authored on 2009-10-17
70
        croak("connect_info '$key' is invald")
71
          unless $VALID_CONNECT_INFO{$key};
add test
yuki-kimoto authored on 2009-10-16
72
    }
73
    
add some method
yuki-kimoto authored on 2009-10-14
74
    my $dbh = DBI->connect(
add test
yuki-kimoto authored on 2009-10-16
75
        $connect_info->{data_source},
add some method
yuki-kimoto authored on 2009-10-14
76
        $connect_info->{user},
77
        $connect_info->{password},
78
        {
79
            RaiseError => 1,
80
            PrintError => 0,
81
            AutoCommit => 1,
82
            %{$connect_info->{options} || {} }
83
        }
84
    );
85
    
86
    $self->dbh($dbh);
87
}
first commit
yuki-kimoto authored on 2009-10-13
88

            
add test
yuki-kimoto authored on 2009-10-17
89
sub create_sql {
90
    my $self = shift;
91
    
92
    my ($sql, @bind) = $self->sql_template->create_sql(@_);
93
    
94
    return ($sql, @bind);
95
}
96

            
add some method
yuki-kimoto authored on 2009-10-14
97
sub query {
try varioud way
yuki-kimoto authored on 2009-10-17
98
    my ($self, $template, $values, $filter)  = @_;
99
    
100
    $filter ||= $self->bind_filter;
101
    
102
    my ($sql, @bind) = $self->creqte_sql($template, $values, $filter);
add test
yuki-kimoto authored on 2009-10-17
103
    $self->prepare($sql);
104
    $self->execute(@bind);
105
}
106

            
107
sub query_raw_sql {
108
    my ($self, $sql, @bind) = @_;
109
    $self->prepare($sql);
110
    $self->execute(@bind);
111
}
112

            
113
Object::Simple->build_class;
114

            
115
package DBI::Custom::SQLTemplate;
116
use Object::Simple;
try various way
yuki-kimoto authored on 2009-10-17
117
use Carp 'croak';
add test
yuki-kimoto authored on 2009-10-17
118

            
try varioud way
yuki-kimoto authored on 2009-10-17
119
### Attributes;
try various way
yuki-kimoto authored on 2009-10-17
120
sub tag_start   : Attr { default => '{' }
121
sub tag_end     : Attr { default => '}' }
122
sub template    : Attr {};
123
sub tree        : Attr { auto_build => sub { shift->tree([]) } }
124
sub bind_filter : Attr {}
125
sub values      : Attr {}
126
sub upper_case  : Attr {default => 0}
try varioud way
yuki-kimoto authored on 2009-10-17
127

            
add test
yuki-kimoto authored on 2009-10-17
128
sub create_sql {
try varioud way
yuki-kimoto authored on 2009-10-17
129
    my ($self, $template, $values, $filter)  = @_;
130
    
try various way
yuki-kimoto authored on 2009-10-17
131
    $filter ||= $self->bind_filter;
132
    
try varioud way
yuki-kimoto authored on 2009-10-17
133
    $self->parse($template);
134
    
try various way
yuki-kimoto authored on 2009-10-17
135
    my ($sql, @bind) = $self->build_sql({bind_filter => $filter, values => $values});
try varioud way
yuki-kimoto authored on 2009-10-17
136
    
137
    return ($sql, @bind);
138
}
139

            
140
our $TAG_SYNTAX = <<'EOS';
141
[tag]            [expand]
142
{= name}         name = ?
try various way
yuki-kimoto authored on 2009-10-17
143
{<> name}        name <> ?
try varioud way
yuki-kimoto authored on 2009-10-17
144

            
145
{< name}         name < ?
146
{> name}         name > ?
147
{>= name}        name >= ?
148
{<= name}        name <= ?
149

            
150
{like name}      name like ?
151
{in name}        name in [?, ?, ..]
152

            
153
{insert_values}  (key1, key2, key3) values (?, ?, ?)
154
{update_values}  set key1 = ?, key2 = ?, key3 = ?
155
EOS
156

            
try various way
yuki-kimoto authored on 2009-10-17
157
our %VALID_TAG_NAMES = map {$_ => 1} qw/= <> < > >= <= like in insert_values update_set/;
try varioud way
yuki-kimoto authored on 2009-10-17
158
sub parse {
159
    my ($self, $template) = @_;
160
    $self->template($template);
161
    
162
    # Clean start;
163
    delete $self->{tree};
164
    
165
    # Tags
166
    my $tag_start = quotemeta $self->tag_start;
167
    my $tag_end   = quotemeta $self->tag_end;
first commit
yuki-kimoto authored on 2009-10-13
168
    
try varioud way
yuki-kimoto authored on 2009-10-17
169
    # Tokenize
170
    my $state = 'text';
171
    
172
    # Save original template
173
    my $original_template = $template;
174
    
175
    # Text
176
    while ($template =~ s/([^$tag_start]*?)$tag_start([^$tag_end].*?)$tag_end//sm) {
try various way
yuki-kimoto authored on 2009-10-17
177
        my $text = $1;
try varioud way
yuki-kimoto authored on 2009-10-17
178
        my $tag  = $2;
179
        
try various way
yuki-kimoto authored on 2009-10-17
180
        push @{$self->tree}, {type => 'text', args => [$text]} if $text;
try varioud way
yuki-kimoto authored on 2009-10-17
181
        
182
        if ($tag) {
183
            
try various way
yuki-kimoto authored on 2009-10-17
184
            my ($tag_name, @args) = split /\s+/, $tag;
try varioud way
yuki-kimoto authored on 2009-10-17
185
            
try various way
yuki-kimoto authored on 2009-10-17
186
            $tag ||= '';
187
            croak("Tag '$tag' in SQL template is invalid.\n\n" .
188
                  "SQL template tag syntax\n$TAG_SYNTAX\n\n" .
189
                  "Your SQL template is \n$original_template\n\n")
190
              unless $VALID_TAG_NAMES{$tag_name};
try varioud way
yuki-kimoto authored on 2009-10-17
191
            
try various way
yuki-kimoto authored on 2009-10-17
192
            push @{$self->tree}, {type => 'tag', tag_name => $tag_name, args => [@args]};
try varioud way
yuki-kimoto authored on 2009-10-17
193
        }
194
    }
195
    
try various way
yuki-kimoto authored on 2009-10-17
196
    push @{$self->tree}, {type => 'text', args => [$template]} if $template;
first commit
yuki-kimoto authored on 2009-10-13
197
}
198

            
try various way
yuki-kimoto authored on 2009-10-17
199
our %EXPAND_PLACE_HOLDER = map {$_ => 1} qw/= <> < > >= <= like/;
200
sub build_sql {
201
    my ($self, $args) = @_;
202
    
203
    my $tree        = $args->{tree} || $self->tree;
204
    my $bind_filter = $args->{bind_filter} || $self->bind_filter;
205
    my $values      = exists $args->{values} ? $args->{values} : $self->values;
206
    
207
    my @bind_values;
208
    my $sql = '';
209
    foreach my $node (@$tree) {
210
        my $type     = $node->{type};
211
        my $tag_name = $node->{tag_name};
212
        my $args     = $node->{args};
213
        
214
        if ($type eq 'text') {
215
            # Join text
216
            $sql .= $args->[0];
217
        }
218
        elsif ($type eq 'tag') {
219
            if ($EXPAND_PLACE_HOLDER{$tag_name}) {
220
                my $key = $args->[0];
221
                
222
                # Filter Value
223
                if ($bind_filter) {
try various way
yuki-kimoto authored on 2009-10-17
224
                    push @bind_values, scalar $bind_filter->($key, $values->{$key});
try various way
yuki-kimoto authored on 2009-10-17
225
                }
226
                else {
227
                    push @bind_values, $values->{$key};
228
                }
229
                $tag_name = uc $tag_name if $self->upper_case;
230
                my $place_holder = "$key $tag_name ?";
231
                $sql .= $place_holder;
232
            }
try various way
yuki-kimoto authored on 2009-10-17
233
            elsif ($tag_name eq 'insert_values') {
234
                my $statement_keys          = '(';
235
                my $statement_place_holders = '(';
236
                
237
                $values = $values->{insert_values};
238
                
239
                foreach my $key (sort keys %$values) {
240
                    if ($bind_filter) {
241
                        push @bind_values, scalar $bind_filter->($key, $values->{$key});
242
                    }
243
                    else {
244
                        push @bind_values, $values->{$key};
245
                    }
246
                    
247
                    $statement_keys          .= "$key, ";
248
                    $statement_place_holders .= "?, ";
249
                }
250
                
251
                $statement_keys =~ s/, $//;
252
                $statement_keys .= ')';
253
                
254
                $statement_place_holders =~ s/, $//;
255
                $statement_place_holders .= ')';
256
                
257
                $sql .= "$statement_keys values $statement_place_holders";
258
            }
259
            elsif ($tag_name eq 'update_set') {
260
                my $statement          = 'set ';
261
                
262
                $values = $values->{update_set};
263
                
264
                foreach my $key (sort keys %$values) {
265
                    if ($bind_filter) {
266
                        push @bind_values, scalar $bind_filter->($key, $values->{$key});
267
                    }
268
                    else {
269
                        push @bind_values, $values->{$key};
270
                    }
271
                    
272
                    $statement          .= "$key = ?, ";
273
                }
274
                
275
                $statement =~ s/, $//;
276
                
277
                $sql .= $statement;
278
            }
try various way
yuki-kimoto authored on 2009-10-17
279
        }
280
    }
281
    $sql .= ';' unless $sql =~ /;$/;
282
    return ($sql, @bind_values);
283
}
try varioud way
yuki-kimoto authored on 2009-10-17
284

            
285

            
first commit
yuki-kimoto authored on 2009-10-13
286
Object::Simple->build_class;
287

            
288
=head1 NAME
289

            
add test
yuki-kimoto authored on 2009-10-17
290
DBI::Custom - Customizable simple DBI
first commit
yuki-kimoto authored on 2009-10-13
291

            
292
=head1 VERSION
293

            
add test
yuki-kimoto authored on 2009-10-16
294
Version 0.0101
first commit
yuki-kimoto authored on 2009-10-13
295

            
296
=cut
297

            
298
=head1 SYNOPSIS
299

            
add test
yuki-kimoto authored on 2009-10-16
300
  my $dbi = DBI::Custom->new;
first commit
yuki-kimoto authored on 2009-10-13
301

            
add test
yuki-kimoto authored on 2009-10-16
302
=head1 METHODS
first commit
yuki-kimoto authored on 2009-10-13
303

            
add test
yuki-kimoto authored on 2009-10-16
304
=head2 add_filter
first commit
yuki-kimoto authored on 2009-10-13
305

            
add test
yuki-kimoto authored on 2009-10-16
306
=head2 bind_filter
first commit
yuki-kimoto authored on 2009-10-13
307

            
add test
yuki-kimoto authored on 2009-10-16
308
=head2 clone
first commit
yuki-kimoto authored on 2009-10-13
309

            
add test
yuki-kimoto authored on 2009-10-16
310
=head2 connect
first commit
yuki-kimoto authored on 2009-10-13
311

            
add test
yuki-kimoto authored on 2009-10-16
312
=head2 connect_info
first commit
yuki-kimoto authored on 2009-10-13
313

            
add test
yuki-kimoto authored on 2009-10-16
314
=head2 dbh
first commit
yuki-kimoto authored on 2009-10-13
315

            
add test
yuki-kimoto authored on 2009-10-16
316
=head2 fetch_filter
first commit
yuki-kimoto authored on 2009-10-13
317

            
add test
yuki-kimoto authored on 2009-10-16
318
=head2 filters
first commit
yuki-kimoto authored on 2009-10-13
319

            
add test
yuki-kimoto authored on 2009-10-16
320
=head2 initialize_model
first commit
yuki-kimoto authored on 2009-10-13
321

            
add test
yuki-kimoto authored on 2009-10-16
322
=head2 model
first commit
yuki-kimoto authored on 2009-10-13
323

            
add test
yuki-kimoto authored on 2009-10-16
324
=head2 new
325

            
326
=head2 query
first commit
yuki-kimoto authored on 2009-10-13
327

            
add test
yuki-kimoto authored on 2009-10-17
328
=head2 create_sql
329

            
330
=head2 query_raw_sql
331

            
332
=head2 sql_template
333

            
first commit
yuki-kimoto authored on 2009-10-13
334
=head1 AUTHOR
335

            
336
Yuki Kimoto, C<< <kimoto.yuki at gmail.com> >>
337

            
338
=head1 BUGS
339

            
340
Please report any bugs or feature requests to C<bug-dbi-custom at rt.cpan.org>, or through
341
the web interface at L<http://rt.cpan.org/NoAuth/ReportBug.html?Queue=DBI-Custom>.  I will be notified, and then you'll
342
automatically be notified of progress on your bug as I make changes.
343

            
344

            
345

            
346

            
347
=head1 SUPPORT
348

            
349
You can find documentation for this module with the perldoc command.
350

            
351
    perldoc DBI::Custom
352

            
353

            
354
You can also look for information at:
355

            
356
=over 4
357

            
358
=item * RT: CPAN's request tracker
359

            
360
L<http://rt.cpan.org/NoAuth/Bugs.html?Dist=DBI-Custom>
361

            
362
=item * AnnoCPAN: Annotated CPAN documentation
363

            
364
L<http://annocpan.org/dist/DBI-Custom>
365

            
366
=item * CPAN Ratings
367

            
368
L<http://cpanratings.perl.org/d/DBI-Custom>
369

            
370
=item * Search CPAN
371

            
372
L<http://search.cpan.org/dist/DBI-Custom/>
373

            
374
=back
375

            
376

            
377
=head1 ACKNOWLEDGEMENTS
378

            
379

            
380
=head1 COPYRIGHT & LICENSE
381

            
382
Copyright 2009 Yuki Kimoto, all rights reserved.
383

            
384
This program is free software; you can redistribute it and/or modify it
385
under the same terms as Perl itself.
386

            
387

            
388
=cut
389

            
390
1; # End of DBI::Custom