Showing 2 changed files with 146 additions and 102 deletions
+142 -98
lib/DBI/Custom.pm
... ...
@@ -7,10 +7,7 @@ use Carp 'croak';
7 7
 use DBI;
8 8
 
9 9
 # Model
10
-sub prototype : ClassAttr { auto_build => \&_inherit_prototype }
11
-
12
-# Inherit super class prototype
13
-sub _inherit_prototype {
10
+sub prototype : ClassAttr { auto_build => sub {
14 11
     my $class = shift;
15 12
     my $super = do {
16 13
         no strict 'refs';
... ...
@@ -21,7 +18,7 @@ sub _inherit_prototype {
21 18
                          : $class->Object::Simple::new;
22 19
     
23 20
     $class->prototype($prototype);
24
-}
21
+}}
25 22
 
26 23
 # New
27 24
 sub new {
... ...
@@ -42,11 +39,16 @@ sub initialize_class {
42 39
 sub clone {
43 40
     my $self = shift;
44 41
     my $new = $self->Object::Simple::new;
42
+    
45 43
     $new->connect_info(%{$self->connect_info || {}});
44
+    $new->connect_info->{options} = \%{$self->connect_info->{options}};
45
+    
46 46
     $new->filters(%{$self->filters || {}});
47 47
     $new->bind_filter($self->bind_filter);
48 48
     $new->fetch_filter($self->fetch_filter);
49 49
     $new->result_class($self->result_class);
50
+    
51
+    $new->sql_template($self->sql_template->clone);
50 52
 }
51 53
 
52 54
 # Attribute
... ...
@@ -76,8 +78,9 @@ sub auto_commit {
76 78
     return $self->dbh->{AutoCommit};
77 79
 }
78 80
 
79
-
80
-our %VALID_CONNECT_INFO = map {$_ => 1} qw/data_source user password options/;
81
+sub valid_connect_info : Attr { default => sub {
82
+    {map {$_ => 1} qw/data_source user password options/}
83
+}}
81 84
 
82 85
 # Connect
83 86
 sub connect {
... ...
@@ -86,7 +89,7 @@ sub connect {
86 89
     
87 90
     foreach my $key (keys %{$self->connect_info}) {
88 91
         croak("connect_info '$key' is wrong name")
89
-          unless $VALID_CONNECT_INFO{$key};
92
+          unless $self->valid_connect_info->{$key};
90 93
     }
91 94
     
92 95
     my $dbh = DBI->connect(
... ...
@@ -132,18 +135,24 @@ sub reconnect {
132 135
     $self->connect;
133 136
 }
134 137
 
135
-# Commit
136
-sub commit {
137
-    my $self = shift;
138
-    croak("Connection is not established") unless $self->connected;
139
-    return $self->dbh->commit;
140
-}
141
-
142
-# Rollback
143
-sub rollback {
144
-    my $self = shift;
145
-    croak("Connection is not established") unless $self->connected;
146
-    return $self->dbh->rollback;
138
+# Run tranzaction
139
+sub run_tranzaction {
140
+    my ($self, $tranzaction) = @_;
141
+    
142
+    $self->auto_commit(0);
143
+    
144
+    eval {
145
+        $tranzaction->();
146
+        $self->dbh->commit;
147
+    };
148
+    
149
+    if ($@) {
150
+        my $tranzaction_error = $@;
151
+        
152
+        $self->dbh->rollback or croak("$@ and rollback also failed");
153
+        croak("$tranzaction_error");
154
+    }
155
+    $self->auto_commit(1);
147 156
 }
148 157
 
149 158
 sub dbh_option {
... ...
@@ -334,6 +343,17 @@ package DBI::Custom::SQL::Template;
334 343
 use Object::Simple;
335 344
 use Carp 'croak';
336 345
 
346
+sub clone {
347
+    my $self = shift;
348
+    my $new = $self->Object::Simple::new;
349
+    
350
+    $new->tag_start($self->tag_start);
351
+    $new->tag_end($self->tag_end);
352
+    $new->bind_filter($self->bind_filter);
353
+    $new->upper_case($self->upper_case);
354
+    $new->tag_syntax($self->tag_syntax);
355
+}
356
+
337 357
 ### Attributes;
338 358
 sub tag_start   : Attr { default => '{' }
339 359
 sub tag_end     : Attr { default => '}' }
... ...
@@ -355,8 +375,7 @@ sub create_sql {
355 375
     return ($sql, @bind);
356 376
 }
357 377
 
358
-our $TAG_SYNTAX = <<'EOS';
359
-[tag]            [expand]
378
+sub tag_syntax : Attr { default => <<'EOS' };
360 379
 {? name}         ?
361 380
 {= name}         name = ?
362 381
 {<> name}        name <> ?
... ...
@@ -373,7 +392,6 @@ our $TAG_SYNTAX = <<'EOS';
373 392
 {update_values}  set key1 = ?, key2 = ?, key3 = ?
374 393
 EOS
375 394
 
376
-our %VALID_TAG_NAMES = map {$_ => 1} qw/= <> < > >= <= like in insert_values update_set/;
377 395
 sub parse {
378 396
     my ($self, $template) = @_;
379 397
     $self->template($template);
... ...
@@ -403,10 +421,13 @@ sub parse {
403 421
             my ($tag_name, @args) = split /\s+/, $tag;
404 422
             
405 423
             $tag ||= '';
406
-            croak("Tag '$tag' in SQL template is not exist.\n\n" .
407
-                  "SQL template tag syntax\n$TAG_SYNTAX\n\n" .
408
-                  "Your SQL template is \n$original_template\n\n")
409
-              unless $VALID_TAG_NAMES{$tag_name};
424
+            unless ($self->tag_processors->{$tag_name}) {
425
+                my $tag_syntax = $self->tag_syntax;
426
+                croak("Tag '$tag' in SQL template is not exist.\n\n" .
427
+                      "SQL template tag syntax\n" .
428
+                      "$tag_syntax\n\n" .
429
+                      "Your SQL template is \n$original_template\n\n");
430
+            }
410 431
             
411 432
             push @{$self->tree}, {type => 'tag', tag_name => $tag_name, args => [@args]};
412 433
         }
... ...
@@ -415,7 +436,6 @@ sub parse {
415 436
     push @{$self->tree}, {type => 'text', args => [$template]} if $template;
416 437
 }
417 438
 
418
-our %EXPAND_PLACE_HOLDER = map {$_ => 1} qw/= <> < > >= <= like/;
419 439
 sub build_sql {
420 440
     my ($self, $args) = @_;
421 441
     
... ...
@@ -423,7 +443,7 @@ sub build_sql {
423 443
     my $bind_filter = $args->{bind_filter} || $self->bind_filter;
424 444
     my $values      = exists $args->{values} ? $args->{values} : $self->values;
425 445
     
426
-    my @bind_values;
446
+    my @bind_values_all;
427 447
     my $sql = '';
428 448
     foreach my $node (@$tree) {
429 449
         my $type     = $node->{type};
... ...
@@ -435,86 +455,65 @@ sub build_sql {
435 455
             $sql .= $args->[0];
436 456
         }
437 457
         elsif ($type eq 'tag') {
438
-            if ($EXPAND_PLACE_HOLDER{$tag_name}) {
439
-                my $key = $args->[0];
440
-                
441
-                # Filter Value
442
-                if ($bind_filter) {
443
-                    push @bind_values, scalar $bind_filter->($key, $values->{$key});
444
-                }
445
-                else {
446
-                    push @bind_values, $values->{$key};
447
-                }
448
-                $tag_name = uc $tag_name if $self->upper_case;
449
-                my $place_holder = "$key $tag_name ?";
450
-                $sql .= $place_holder;
451
-            }
452
-            elsif ($tag_name eq 'insert_values') {
453
-                my $statement_keys          = '(';
454
-                my $statement_place_holders = '(';
455
-                
456
-                $values = $values->{insert_values};
457
-                
458
-                foreach my $key (sort keys %$values) {
459
-                    if ($bind_filter) {
460
-                        push @bind_values, scalar $bind_filter->($key, $values->{$key});
461
-                    }
462
-                    else {
463
-                        push @bind_values, $values->{$key};
464
-                    }
465
-                    
466
-                    $statement_keys          .= "$key, ";
467
-                    $statement_place_holders .= "?, ";
468
-                }
469
-                
470
-                $statement_keys =~ s/, $//;
471
-                $statement_keys .= ')';
472
-                
473
-                $statement_place_holders =~ s/, $//;
474
-                $statement_place_holders .= ')';
475
-                
476
-                $sql .= "$statement_keys values $statement_place_holders";
477
-            }
478
-            elsif ($tag_name eq 'update_set') {
479
-                my $statement          = 'set ';
480
-                
481
-                $values = $values->{update_set};
482
-                
483
-                foreach my $key (sort keys %$values) {
484
-                    if ($bind_filter) {
485
-                        push @bind_values, scalar $bind_filter->($key, $values->{$key});
486
-                    }
487
-                    else {
488
-                        push @bind_values, $values->{$key};
489
-                    }
490
-                    
491
-                    $statement          .= "$key = ?, ";
492
-                }
458
+            my $tag_processor = $self->tag_processors->{$type};
459
+            
460
+            croak("Tag processor '$type' must be code reference")
461
+              unless ref $tag_processor eq 'CODE';
462
+            
463
+            my ($expand, @bind_values)
464
+              = $self->tag_processors->{$type}->($tag_name, $args, $values,
465
+                                                 $bind_filter, $self);
466
+            
467
+            unless ($self->place_holder_count($expand) eq @bind_values) {
468
+                require Data::Dumper;
493 469
                 
494
-                $statement =~ s/, $//;
470
+                my $bind_values_dump
471
+                  = Data::Dumper->Dump([\@bind_values], ['@bind_values']);
495 472
                 
496
-                $sql .= $statement;
473
+                croak("Place holder count must be same as bind value count\n" .
474
+                      "Tag        : $tag_name\n" .
475
+                      "Expand     : $expand\n" .
476
+                      "Bind values: $bind_values_dump\n");
497 477
             }
478
+            push @bind_values_all, @bind_values;
479
+            $sql .= $expand;
498 480
         }
499 481
     }
500 482
     $sql .= ';' unless $sql =~ /;$/;
501
-    return ($sql, @bind_values);
483
+    return ($sql, @bind_values_all);
484
+}
485
+
486
+sub _placeholder_count {
487
+    my ($self, $expand) = @_;
488
+    $expand ||= '';
489
+    
490
+    my $count = 0;
491
+    my $pos   = 0;
492
+    while ((my $pos = index $expand, $pos) != -1) {
493
+        $count++;
494
+    }
495
+    return $count;
502 496
 }
503 497
 
504 498
 sub tag_processors : Attr {type => 'hash', deref => 1, auto_build => sub { 
505 499
     shift->tag_processors(
506
-        '='    => \&DBI::Custom::SQL::Template::TagProcessor::expand_place_holder,
507
-        '<>'   => \&DBI::Custom::SQL::Template::TagProcessor::expand_place_holder,
508
-        '<'    => \&DBI::Custom::SQL::Template::TagProcessor::expand_place_holder,
509
-        '>='   => \&DBI::Custom::SQL::Template::TagProcessor::expand_place_holder,
510
-        '<='   => \&DBI::Custom::SQL::Template::TagProcessor::expand_place_holder,
511
-        'like' => \&DBI::Custom::SQL::Template::TagProcessor::expand_place_holder,
512
-        'in'   => \&DBI::Custom::SQL::Template::TagProcessor::expand_place_holder
500
+        '?'             => \&DBI::Custom::SQL::Template::TagProcessor::expand_place_holder,
501
+        '='             => \&DBI::Custom::SQL::Template::TagProcessor::expand_place_holder,
502
+        '<>'            => \&DBI::Custom::SQL::Template::TagProcessor::expand_place_holder,
503
+        '<'             => \&DBI::Custom::SQL::Template::TagProcessor::expand_place_holder,
504
+        '>='            => \&DBI::Custom::SQL::Template::TagProcessor::expand_place_holder,
505
+        '<='            => \&DBI::Custom::SQL::Template::TagProcessor::expand_place_holder,
506
+        'like'          => \&DBI::Custom::SQL::Template::TagProcessor::expand_place_holder,
507
+        'in'            => \&DBI::Custom::SQL::Template::TagProcessor::expand_place_holder,
508
+        'insert_values' => \&DBI::Custom::SQL::Template::TagProcessor::expand_insert_values,
509
+        'update_set'    => \&DBI::Custom::SQL::Template::TagProcessor::expand_update_set
513 510
     );
514 511
 }}
515 512
 
516 513
 sub add_tag_processor {
517
-    
514
+    my $class = shift;
515
+    my $tag_processors = ref $_[0] eq 'HASH' ? $_[0] : {@_};
516
+    $class->tag_processor(%{$class->tag_processor}, %{$tag_processors});
518 517
 }
519 518
 
520 519
 Object::Simple->build_class;
... ...
@@ -550,6 +549,7 @@ sub expand_place_holder {
550 549
             push @bind_values, $values->{$key};
551 550
         }
552 551
     }
552
+    
553 553
     if ($bind_filter) {
554 554
         if ($tag_name eq 'in') {
555 555
             for (my $i = 0; $i < @$values; $i++) {
... ...
@@ -582,7 +582,53 @@ sub expand_place_holder {
582 582
         $expand = "$key $tag_name ?";
583 583
     }
584 584
     
585
-    return ($expand, \@bind_values);
585
+    return ($expand, @bind_values);
586
+}
587
+
588
+sub expand_insert_values {
589
+    my ($tag_name, $args, $values, $bind_filter, $sql_tmpl_obj) = @_;
590
+    
591
+    my $insert_keys = '(';
592
+    my $place_holders = '(';
593
+    
594
+    $values = $args->[0] ? $values->{$args->[0]} : $values->{insert_values};
595
+    
596
+    my @bind_values;
597
+    foreach my $key (sort keys %$values) {
598
+        $bind_filter ? push @bind_values, scalar $bind_filter->($key, $values->{$key})
599
+                     : push @bind_values, $values->{$key};
600
+        
601
+        $insert_keys   .= "$key, ";
602
+        $place_holders .= "?, ";
603
+    }
604
+    
605
+    $insert_keys =~ s/, $//;
606
+    $insert_keys .= ')';
607
+    
608
+    $place_holders =~ s/, $//;
609
+    $place_holders .= ')';
610
+    
611
+    my $expand = $sql_tmpl_obj->uppser_case ? "$insert_keys VALUES $place_holders"
612
+                                            : "$insert_keys values $place_holders";
613
+    
614
+    return ($expand, @bind_values);
615
+}
616
+
617
+sub expand_update_set {
618
+    my ($tag_name, $args, $values, $bind_filter, $sql_tmpl_obj) = @_;
619
+    
620
+    my $expand = $sql_tmpl_obj->uppser_case ? 'SET ' : 'set ';
621
+    $values = $args->[0] ? $values->{$args->[0]} : $values->{update_set};
622
+    
623
+    my @bind_values;
624
+    foreach my $key (sort keys %$values) {
625
+        $bind_filter ? push @bind_values, scalar $bind_filter->($key, $values->{$key})
626
+                     : push @bind_values, $values->{$key};
627
+        
628
+        $expand .= "$key = ?, ";
629
+    }
630
+    $expand =~ s/, $//;
631
+    return ($expand, @bind_values);
586 632
 }
587 633
 
588 634
 
... ...
@@ -607,8 +653,6 @@ Version 0.0101
607 653
 
608 654
 =head2 add_filter
609 655
 
610
-    
611
-
612 656
 =head2 bind_filter
613 657
 
614 658
 =head2 clone
+4 -4
t/01-core.t
... ...
@@ -34,9 +34,9 @@ use Scalar::Util qw/blessed/;
34 34
     use base 'DBI::Custom';
35 35
     
36 36
     __PACKAGE__->initialize_class(sub {
37
-        my $model = shift;
37
+        my $class = shift;
38 38
         
39
-        $model
39
+        $class
40 40
           ->connect_info(
41 41
             user => 'a',
42 42
             password => 'b',
... ...
@@ -102,9 +102,9 @@ use Scalar::Util qw/blessed/;
102 102
     use base 'DBI::Custom::T1';
103 103
     
104 104
     __PACKAGE__->initialize_class(sub {
105
-        my $model = shift;
105
+        my $class = shift;
106 106
         
107
-        $model
107
+        $class
108 108
           ->connect_info(
109 109
             user => 'ao',
110 110
             password => 'bo',