... | ... |
@@ -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 |
... | ... |
@@ -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', |