DBIx-Custom / lib / DBI / Custom.pm /
Newer Older
502 lines | 11.591kb
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);
add various things
yuki-kimoto authored on 2009-10-17
49
    $new->result_class($self->result_class);
cleanup
yuki-kimoto authored on 2009-10-14
50
}
51

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

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

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

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

            
add various things
yuki-kimoto authored on 2009-10-17
65

            
add test
yuki-kimoto authored on 2009-10-16
66
our %VALID_CONNECT_INFO = map {$_ => 1} qw/data_source user password options/;
cleanup
yuki-kimoto authored on 2009-10-14
67

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

            
add tests
yuki-kimoto authored on 2009-10-18
94
sub DESTROY {
95
    my $self = shift;
96
    $self->disconnect;
97
}
98

            
add various things
yuki-kimoto authored on 2009-10-17
99
# Is connected?
100
sub connected {
101
    my $self = shift;
add tests
yuki-kimoto authored on 2009-10-18
102
    return exists $self->{dbh} && eval {$self->dbh->can('prepare')};
add various things
yuki-kimoto authored on 2009-10-17
103
}
104

            
105
# Disconnect
106
sub disconnect {
107
    my $self = shift;
add tests
yuki-kimoto authored on 2009-10-18
108
    if ($self->connected) {
add various things
yuki-kimoto authored on 2009-10-17
109
        $self->dbh->disconnect;
110
        delete $self->{dbh};
111
    }
112
}
113

            
114
# Reconnect
115
sub reconnect {
116
    my $self = shift;
add tests
yuki-kimoto authored on 2009-10-18
117
    $self->disconnect if $self->connected;
add various things
yuki-kimoto authored on 2009-10-17
118
    $self->connect;
119
}
120

            
add tests
yuki-kimoto authored on 2009-10-18
121
sub dbh_option {
122
    my $self = shift;
123
    croak("Not connected") unless $self->connected;
124
    my $dbh = $self->dbh;
125
    if (@_ > 1) {
126
        $dbh->{$_[0]} = $_[1];
127
        return $self;
128
    }
129
    return $dbh->{$_[0]}
130
}
131

            
add various things
yuki-kimoto authored on 2009-10-17
132

            
add test
yuki-kimoto authored on 2009-10-17
133
sub create_sql {
134
    my $self = shift;
135
    
136
    my ($sql, @bind) = $self->sql_template->create_sql(@_);
137
    
138
    return ($sql, @bind);
139
}
140

            
add some method
yuki-kimoto authored on 2009-10-14
141
sub query {
try varioud way
yuki-kimoto authored on 2009-10-17
142
    my ($self, $template, $values, $filter)  = @_;
143
    
add tests
yuki-kimoto authored on 2009-10-18
144
    my $sth_options;
145
    
146
    # Rearrange when argumets is hash referecne 
147
    if (ref $template eq 'HASH') {
148
        my $args = $template;
149
        ($template, $values, $filter, $sth_options)
150
          = @{$args}{qw/template values filter sth_options/};
151
    }
152
    
try varioud way
yuki-kimoto authored on 2009-10-17
153
    $filter ||= $self->bind_filter;
154
    
add various things
yuki-kimoto authored on 2009-10-17
155
    my ($sql, @bind) = $self->create_sql($template, $values, $filter);
156
    my $sth = $self->dbh->prepare($sql);
add tests
yuki-kimoto authored on 2009-10-18
157
    
158
    if ($sth_options) {
159
        foreach my $key (keys %$sth_options) {
160
            $sth->{$key} = $sth_options->{$key};
161
        }
162
    }
163
    
add various things
yuki-kimoto authored on 2009-10-17
164
    $sth->execute(@bind);
165
    
166
    # Select
167
    if ($sth->{NUM_OF_FIELDS}) {
168
        my $result_class = $self->result_class;
169
        my $result = $result_class->new({sth => $sth});
170
        return $result;
171
    }
172
    return;
add test
yuki-kimoto authored on 2009-10-17
173
}
174

            
add tests
yuki-kimoto authored on 2009-10-18
175

            
add test
yuki-kimoto authored on 2009-10-17
176
sub query_raw_sql {
177
    my ($self, $sql, @bind) = @_;
add various things
yuki-kimoto authored on 2009-10-17
178
    my $sth = $self->dbh->prepare($sql);
179
    $sth->execute(@bind);
180
    return $sth;
add test
yuki-kimoto authored on 2009-10-17
181
}
182

            
add tests
yuki-kimoto authored on 2009-10-18
183
sub auto_commit : Attr {}
184

            
add various things
yuki-kimoto authored on 2009-10-17
185

            
add test
yuki-kimoto authored on 2009-10-17
186
Object::Simple->build_class;
187

            
add various things
yuki-kimoto authored on 2009-10-17
188
package DBI::Custom::Result;
189
use Object::Simple;
190

            
191
sub sth : Attr {};
192

            
193
sub fetchrow_arrayref {
194
    my $self = shift;
195
    $self->sth;
196
    
197
    
198
}
199

            
200

            
201
*fetch = \&fetchrow_arrayref;
202

            
203
sub err    { shift->sth->err }
204
sub errstr { shift->sth->errstr }
205
sub finish { shift->sth->finish }
206
sub rows   { shift->sth->rows }
207
sub state  { shift->sth->state }
208

            
209
Object::Simple->build_class;
210

            
211

            
add test
yuki-kimoto authored on 2009-10-17
212
package DBI::Custom::SQLTemplate;
213
use Object::Simple;
try various way
yuki-kimoto authored on 2009-10-17
214
use Carp 'croak';
add test
yuki-kimoto authored on 2009-10-17
215

            
try varioud way
yuki-kimoto authored on 2009-10-17
216
### Attributes;
try various way
yuki-kimoto authored on 2009-10-17
217
sub tag_start   : Attr { default => '{' }
218
sub tag_end     : Attr { default => '}' }
219
sub template    : Attr {};
220
sub tree        : Attr { auto_build => sub { shift->tree([]) } }
221
sub bind_filter : Attr {}
222
sub values      : Attr {}
223
sub upper_case  : Attr {default => 0}
try varioud way
yuki-kimoto authored on 2009-10-17
224

            
add test
yuki-kimoto authored on 2009-10-17
225
sub create_sql {
try varioud way
yuki-kimoto authored on 2009-10-17
226
    my ($self, $template, $values, $filter)  = @_;
227
    
try various way
yuki-kimoto authored on 2009-10-17
228
    $filter ||= $self->bind_filter;
229
    
try varioud way
yuki-kimoto authored on 2009-10-17
230
    $self->parse($template);
231
    
try various way
yuki-kimoto authored on 2009-10-17
232
    my ($sql, @bind) = $self->build_sql({bind_filter => $filter, values => $values});
try varioud way
yuki-kimoto authored on 2009-10-17
233
    
234
    return ($sql, @bind);
235
}
236

            
237
our $TAG_SYNTAX = <<'EOS';
238
[tag]            [expand]
239
{= name}         name = ?
try various way
yuki-kimoto authored on 2009-10-17
240
{<> name}        name <> ?
try varioud way
yuki-kimoto authored on 2009-10-17
241

            
242
{< name}         name < ?
243
{> name}         name > ?
244
{>= name}        name >= ?
245
{<= name}        name <= ?
246

            
247
{like name}      name like ?
248
{in name}        name in [?, ?, ..]
249

            
250
{insert_values}  (key1, key2, key3) values (?, ?, ?)
251
{update_values}  set key1 = ?, key2 = ?, key3 = ?
252
EOS
253

            
try various way
yuki-kimoto authored on 2009-10-17
254
our %VALID_TAG_NAMES = map {$_ => 1} qw/= <> < > >= <= like in insert_values update_set/;
try varioud way
yuki-kimoto authored on 2009-10-17
255
sub parse {
256
    my ($self, $template) = @_;
257
    $self->template($template);
258
    
259
    # Clean start;
260
    delete $self->{tree};
261
    
262
    # Tags
263
    my $tag_start = quotemeta $self->tag_start;
264
    my $tag_end   = quotemeta $self->tag_end;
first commit
yuki-kimoto authored on 2009-10-13
265
    
try varioud way
yuki-kimoto authored on 2009-10-17
266
    # Tokenize
267
    my $state = 'text';
268
    
269
    # Save original template
270
    my $original_template = $template;
271
    
272
    # Text
273
    while ($template =~ s/([^$tag_start]*?)$tag_start([^$tag_end].*?)$tag_end//sm) {
try various way
yuki-kimoto authored on 2009-10-17
274
        my $text = $1;
try varioud way
yuki-kimoto authored on 2009-10-17
275
        my $tag  = $2;
276
        
try various way
yuki-kimoto authored on 2009-10-17
277
        push @{$self->tree}, {type => 'text', args => [$text]} if $text;
try varioud way
yuki-kimoto authored on 2009-10-17
278
        
279
        if ($tag) {
280
            
try various way
yuki-kimoto authored on 2009-10-17
281
            my ($tag_name, @args) = split /\s+/, $tag;
try varioud way
yuki-kimoto authored on 2009-10-17
282
            
try various way
yuki-kimoto authored on 2009-10-17
283
            $tag ||= '';
284
            croak("Tag '$tag' in SQL template is invalid.\n\n" .
285
                  "SQL template tag syntax\n$TAG_SYNTAX\n\n" .
286
                  "Your SQL template is \n$original_template\n\n")
287
              unless $VALID_TAG_NAMES{$tag_name};
try varioud way
yuki-kimoto authored on 2009-10-17
288
            
try various way
yuki-kimoto authored on 2009-10-17
289
            push @{$self->tree}, {type => 'tag', tag_name => $tag_name, args => [@args]};
try varioud way
yuki-kimoto authored on 2009-10-17
290
        }
291
    }
292
    
try various way
yuki-kimoto authored on 2009-10-17
293
    push @{$self->tree}, {type => 'text', args => [$template]} if $template;
first commit
yuki-kimoto authored on 2009-10-13
294
}
295

            
try various way
yuki-kimoto authored on 2009-10-17
296
our %EXPAND_PLACE_HOLDER = map {$_ => 1} qw/= <> < > >= <= like/;
297
sub build_sql {
298
    my ($self, $args) = @_;
299
    
300
    my $tree        = $args->{tree} || $self->tree;
301
    my $bind_filter = $args->{bind_filter} || $self->bind_filter;
302
    my $values      = exists $args->{values} ? $args->{values} : $self->values;
303
    
304
    my @bind_values;
305
    my $sql = '';
306
    foreach my $node (@$tree) {
307
        my $type     = $node->{type};
308
        my $tag_name = $node->{tag_name};
309
        my $args     = $node->{args};
310
        
311
        if ($type eq 'text') {
312
            # Join text
313
            $sql .= $args->[0];
314
        }
315
        elsif ($type eq 'tag') {
316
            if ($EXPAND_PLACE_HOLDER{$tag_name}) {
317
                my $key = $args->[0];
318
                
319
                # Filter Value
320
                if ($bind_filter) {
try various way
yuki-kimoto authored on 2009-10-17
321
                    push @bind_values, scalar $bind_filter->($key, $values->{$key});
try various way
yuki-kimoto authored on 2009-10-17
322
                }
323
                else {
324
                    push @bind_values, $values->{$key};
325
                }
326
                $tag_name = uc $tag_name if $self->upper_case;
327
                my $place_holder = "$key $tag_name ?";
328
                $sql .= $place_holder;
329
            }
try various way
yuki-kimoto authored on 2009-10-17
330
            elsif ($tag_name eq 'insert_values') {
331
                my $statement_keys          = '(';
332
                my $statement_place_holders = '(';
333
                
334
                $values = $values->{insert_values};
335
                
336
                foreach my $key (sort keys %$values) {
337
                    if ($bind_filter) {
338
                        push @bind_values, scalar $bind_filter->($key, $values->{$key});
339
                    }
340
                    else {
341
                        push @bind_values, $values->{$key};
342
                    }
343
                    
344
                    $statement_keys          .= "$key, ";
345
                    $statement_place_holders .= "?, ";
346
                }
347
                
348
                $statement_keys =~ s/, $//;
349
                $statement_keys .= ')';
350
                
351
                $statement_place_holders =~ s/, $//;
352
                $statement_place_holders .= ')';
353
                
354
                $sql .= "$statement_keys values $statement_place_holders";
355
            }
356
            elsif ($tag_name eq 'update_set') {
357
                my $statement          = 'set ';
358
                
359
                $values = $values->{update_set};
360
                
361
                foreach my $key (sort keys %$values) {
362
                    if ($bind_filter) {
363
                        push @bind_values, scalar $bind_filter->($key, $values->{$key});
364
                    }
365
                    else {
366
                        push @bind_values, $values->{$key};
367
                    }
368
                    
369
                    $statement          .= "$key = ?, ";
370
                }
371
                
372
                $statement =~ s/, $//;
373
                
374
                $sql .= $statement;
375
            }
try various way
yuki-kimoto authored on 2009-10-17
376
        }
377
    }
378
    $sql .= ';' unless $sql =~ /;$/;
379
    return ($sql, @bind_values);
380
}
try varioud way
yuki-kimoto authored on 2009-10-17
381

            
382

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

            
add various things
yuki-kimoto authored on 2009-10-17
385
package DBI::Custom;
386
1;
387

            
first commit
yuki-kimoto authored on 2009-10-13
388
=head1 NAME
389

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

            
392
=head1 VERSION
393

            
add test
yuki-kimoto authored on 2009-10-16
394
Version 0.0101
first commit
yuki-kimoto authored on 2009-10-13
395

            
396
=cut
397

            
398
=head1 SYNOPSIS
399

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

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

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

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

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

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

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

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

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

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

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

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

            
add test
yuki-kimoto authored on 2009-10-16
424
=head2 new
425

            
426
=head2 query
first commit
yuki-kimoto authored on 2009-10-13
427

            
add test
yuki-kimoto authored on 2009-10-17
428
=head2 create_sql
429

            
430
=head2 query_raw_sql
431

            
432
=head2 sql_template
433

            
add tests
yuki-kimoto authored on 2009-10-18
434
=head2 auto_commit
435

            
436
=head2 connected
437

            
438
=head2 dbh_option
439

            
440
=head2 disconnect
441

            
442
=head2 reconnect
443

            
444
=head2 result_class
445

            
first commit
yuki-kimoto authored on 2009-10-13
446
=head1 AUTHOR
447

            
448
Yuki Kimoto, C<< <kimoto.yuki at gmail.com> >>
449

            
450
=head1 BUGS
451

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

            
456

            
457

            
458

            
459
=head1 SUPPORT
460

            
461
You can find documentation for this module with the perldoc command.
462

            
463
    perldoc DBI::Custom
464

            
465

            
466
You can also look for information at:
467

            
468
=over 4
469

            
470
=item * RT: CPAN's request tracker
471

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

            
474
=item * AnnoCPAN: Annotated CPAN documentation
475

            
476
L<http://annocpan.org/dist/DBI-Custom>
477

            
478
=item * CPAN Ratings
479

            
480
L<http://cpanratings.perl.org/d/DBI-Custom>
481

            
482
=item * Search CPAN
483

            
484
L<http://search.cpan.org/dist/DBI-Custom/>
485

            
486
=back
487

            
488

            
489
=head1 ACKNOWLEDGEMENTS
490

            
491

            
492
=head1 COPYRIGHT & LICENSE
493

            
494
Copyright 2009 Yuki Kimoto, all rights reserved.
495

            
496
This program is free software; you can redistribute it and/or modify it
497
under the same terms as Perl itself.
498

            
499

            
500
=cut
501

            
502
1; # End of DBI::Custom