| ... | ... | 
                  @@ -10,6 +10,9 @@ my $builder = Module::Build->new(  | 
              
| 10 | 10 | 
                       build_requires => {
                 | 
              
| 11 | 11 | 
                  'Test::More' => 0,  | 
              
| 12 | 12 | 
                  },  | 
              
| 13 | 
                  +    requires => {
                 | 
              |
| 14 | 
                  + 'Object::Simple' => 0.0602,  | 
              |
| 15 | 
                  + },  | 
              |
| 13 | 16 | 
                  add_to_cleanup => [ 'DBI-Custom-*' ],  | 
              
| 14 | 17 | 
                  create_makefile_pl => 'traditional',  | 
              
| 15 | 18 | 
                  );  | 
              
| ... | ... | 
                  @@ -5,64 +5,62 @@ our $VERSION = '0.0101';  | 
              
| 5 | 5 | 
                   | 
              
| 6 | 6 | 
                  use Carp 'croak';  | 
              
| 7 | 7 | 
                  use DBI;  | 
              
| 8 | 
                  +use DBI::Custom::SQL::Template;  | 
              |
| 9 | 
                  +  | 
              |
| 10 | 
                  +### Class-Object Accessors  | 
              |
| 11 | 
                  +sub connect_info : ClassObjectAttr { type => 'hash',  auto_build => sub {
                 | 
              |
| 12 | 
                  + shift->Object::Simple::initialize_class_object_attr(  | 
              |
| 13 | 
                  +        default => sub { {} }, clone => sub {
                 | 
              |
| 14 | 
                  + my $value = shift;  | 
              |
| 15 | 
                  +            my $new_value = \%{$value || {}};
                 | 
              |
| 16 | 
                  +            $new_value->{options} = $value->{options} if $value->{options};
                 | 
              |
| 17 | 
                  + return $new_value;  | 
              |
| 18 | 
                  + }  | 
              |
| 19 | 
                  + )  | 
              |
| 20 | 
                  +}}  | 
              |
| 8 | 21 | 
                   | 
              
| 9 | 
                  -# Model  | 
              |
| 10 | 
                  -sub prototype : ClassAttr { auto_build => sub {
                 | 
              |
| 11 | 
                  - my $class = shift;  | 
              |
| 12 | 
                  -    my $super = do {
                 | 
              |
| 13 | 
                  - no strict 'refs';  | 
              |
| 14 | 
                  -        ${"${class}::ISA"}[0];
                 | 
              |
| 15 | 
                  - };  | 
              |
| 16 | 
                  -    my $prototype = eval{$super->can('prototype')}
                 | 
              |
| 17 | 
                  - ? $super->prototype->clone  | 
              |
| 18 | 
                  - : $class->Object::Simple::new;  | 
              |
| 19 | 
                  -  | 
              |
| 20 | 
                  - $class->prototype(bless $prototype, $class);  | 
              |
| 22 | 
                  +sub bind_filter  : ClassObjectAttr { auto_build => sub {
                 | 
              |
| 23 | 
                  + shift->Object::Simple::initialize_class_object_attr(clone => 'scalar')  | 
              |
| 24 | 
                  +}}  | 
              |
| 25 | 
                  +sub fetch_filter : ClassObjectAttr { auto_build => sub {
                 | 
              |
| 26 | 
                  + shift->Object::Simple::initialize_class_object_attr(clone => 'scalar')  | 
              |
| 21 | 27 | 
                  }}  | 
              
| 22 | 28 | 
                   | 
              
| 23 | 
                  -# New  | 
              |
| 24 | 
                  -sub new {
                 | 
              |
| 25 | 
                  - my $invocant = shift;  | 
              |
| 26 | 
                  - my $class = ref $invocant || $invocant;  | 
              |
| 27 | 
                  - my $prototype = $class->prototype;  | 
              |
| 28 | 
                  -    my $self = $class->Object::Simple::new(%{$prototype->clone}, @_);
                 | 
              |
| 29 | 
                  - return bless $self, $class;  | 
              |
| 30 | 
                  -}  | 
              |
| 29 | 
                  +sub filters : ClassObjectAttr { type => 'hash', deref => 1, auto_build => sub {
                 | 
              |
| 30 | 
                  + shift->Object::Simple::initialize_class_object_attr(clone => 'hash')  | 
              |
| 31 | 
                  +}}  | 
              |
| 31 | 32 | 
                   | 
              
| 32 | 
                  -# Clone  | 
              |
| 33 | 
                  -sub clone {
                 | 
              |
| 34 | 
                  - my $self = shift;  | 
              |
| 35 | 
                  - my $new = $self->Object::Simple::new;  | 
              |
| 36 | 
                  -  | 
              |
| 37 | 
                  - # Scalar copy  | 
              |
| 38 | 
                  -    foreach my $attr (qw/bind_filter fetch_filter result_class/) {
                 | 
              |
| 39 | 
                  - $new->$attr($self->$attr);  | 
              |
| 40 | 
                  - }  | 
              |
| 41 | 
                  -  | 
              |
| 42 | 
                  - # Hash ref copy  | 
              |
| 43 | 
                  -    foreach my $attr (qw/connect_info filters valid_connect_info/) {
                 | 
              |
| 44 | 
                  -        $new->$attr(\%{$self->$attr || {}});
                 | 
              |
| 45 | 
                  - }  | 
              |
| 46 | 
                  -  | 
              |
| 47 | 
                  - # Other  | 
              |
| 48 | 
                  -    $new->connect_info->{options} = \%{$self->connect_info->{options}};
                 | 
              |
| 49 | 
                  - $new->sql_template($self->sql_template->clone);  | 
              |
| 50 | 
                  -}  | 
              |
| 33 | 
                  +sub result_class : ClassObjectAttr { auto_build => sub {
                 | 
              |
| 34 | 
                  + shift->Object::Simple::initialize_class_object_attr(clone => 'scalar')  | 
              |
| 35 | 
                  +}}  | 
              |
| 51 | 36 | 
                   | 
              
| 52 | 
                  -# Attribute  | 
              |
| 53 | 
                  -sub connect_info : Attr { type => 'hash',  default => sub { {} } }
                 | 
              |
| 54 | 
                  -sub bind_filter  : Attr {}
                 | 
              |
| 55 | 
                  -sub fetch_filter : Attr {}
                 | 
              |
| 37 | 
                  +sub sql_template : ClassObjectAttr { auto_build => sub {
                 | 
              |
| 38 | 
                  + shift->Object::Simple::initialize_class_object_attr(  | 
              |
| 39 | 
                  +        clone   => sub {my $value = shift; $value ? $value->clone : undef},
                 | 
              |
| 40 | 
                  +        default => sub { DBI::Custom::SQL::Template->new }
                 | 
              |
| 41 | 
                  + )  | 
              |
| 42 | 
                  +}}  | 
              |
| 56 | 43 | 
                   | 
              
| 57 | 
                  -sub filters : Attr { type => 'hash', deref => 1, default => sub { {} } }
                 | 
              |
| 58 | 
                  -sub add_filter { shift->filters(@_) }
                 | 
              |
| 44 | 
                  +sub valid_connect_info : ClassObjectAttr { type => 'hash', deref => 1, auto_build => sub {
                 | 
              |
| 45 | 
                  + shift->Object::Simple::initialize_class_object_attr(  | 
              |
| 46 | 
                  +        default => sub { return {map {$_ => 1} qw/data_source user password options/} },
                 | 
              |
| 47 | 
                  + clone => 'hash'  | 
              |
| 48 | 
                  + )  | 
              |
| 49 | 
                  +}}  | 
              |
| 59 | 50 | 
                   | 
              
| 60 | 
                  -sub result_class : Attr { default => 'DBI::Custom::Result' }
                 | 
              |
| 51 | 
                  +### Object Accessor  | 
              |
| 61 | 52 | 
                   sub dbh          : Attr {}
                 | 
              
| 62 | 
                  -sub sql_template : Attr { default => sub { DBI::Custom::SQL::Template->new } }
                 | 
              |
| 63 | 
                  -sub valid_connect_info : Attr { type => 'hash', deref => 1, default => sub {
                 | 
              |
| 64 | 
                  -    return {map {$_ => 1} qw/data_source user password options/}
                 | 
              |
| 65 | 
                  -}}  | 
              |
| 53 | 
                  +  | 
              |
| 54 | 
                  +  | 
              |
| 55 | 
                  +### Methods  | 
              |
| 56 | 
                  +# Add filter  | 
              |
| 57 | 
                  +sub add_filter {
                 | 
              |
| 58 | 
                  + my $invocant = shift;  | 
              |
| 59 | 
                  +  | 
              |
| 60 | 
                  + my %old_filters = $invocant->filters;  | 
              |
| 61 | 
                  +    my %new_filters = ref $_[0] eq 'HASH' ? %{$_[0]} : @_;
                 | 
              |
| 62 | 
                  + $invocant->filters(%old_filters, %new_filters);  | 
              |
| 63 | 
                  +}  | 
              |
| 66 | 64 | 
                   | 
              
| 67 | 65 | 
                  # Auto commit  | 
              
| 68 | 66 | 
                   sub auto_commit {
                 | 
              
| ... | ... | 
                  @@ -104,6 +102,7 @@ sub connect {
                 | 
              
| 104 | 102 | 
                  return $self;  | 
              
| 105 | 103 | 
                  }  | 
              
| 106 | 104 | 
                   | 
              
| 105 | 
                  +# DESTROY  | 
              |
| 107 | 106 | 
                   sub DESTROY {
                 | 
              
| 108 | 107 | 
                  my $self = shift;  | 
              
| 109 | 108 | 
                  $self->disconnect if $self->connected;  | 
              
| ... | ... | 
                  @@ -223,404 +222,6 @@ sub query_raw_sql {
                 | 
              
| 223 | 222 | 
                   | 
              
| 224 | 223 | 
                  Object::Simple->build_class;  | 
              
| 225 | 224 | 
                   | 
              
| 226 | 
                  -  | 
              |
| 227 | 
                  -package DBI::Custom::Result;  | 
              |
| 228 | 
                  -use Object::Simple;  | 
              |
| 229 | 
                  -  | 
              |
| 230 | 
                  -# Attributes  | 
              |
| 231 | 
                  -sub sth          : Attr {}
                 | 
              |
| 232 | 
                  -sub fetch_filter : Attr {}
                 | 
              |
| 233 | 
                  -  | 
              |
| 234 | 
                  -  | 
              |
| 235 | 
                  -# Fetch (array)  | 
              |
| 236 | 
                  -sub fetch {
                 | 
              |
| 237 | 
                  - my ($self, $type) = @_;  | 
              |
| 238 | 
                  - my $sth = $self->sth;  | 
              |
| 239 | 
                  - my $fetch_filter = $self->fetch_filter;  | 
              |
| 240 | 
                  -  | 
              |
| 241 | 
                  - # Fetch  | 
              |
| 242 | 
                  - my $row = $sth->fetchrow_arrayref;  | 
              |
| 243 | 
                  -  | 
              |
| 244 | 
                  - # Cannot fetch  | 
              |
| 245 | 
                  - return unless $row;  | 
              |
| 246 | 
                  -  | 
              |
| 247 | 
                  - # Filter  | 
              |
| 248 | 
                  -    if ($fetch_filter) {
                 | 
              |
| 249 | 
                  -        my $keys  = $sth->{NAME_lc};
                 | 
              |
| 250 | 
                  -        my $types = $sth->{TYPE};
                 | 
              |
| 251 | 
                  -        for (my $i = 0; $i < @$keys; $i++) {
                 | 
              |
| 252 | 
                  - $row->[$i]= $fetch_filter->($keys->[$i], $row->[$i], $types->[$i],  | 
              |
| 253 | 
                  - $sth, $i);  | 
              |
| 254 | 
                  - }  | 
              |
| 255 | 
                  - }  | 
              |
| 256 | 
                  - return wantarray ? @$row : $row;  | 
              |
| 257 | 
                  -}  | 
              |
| 258 | 
                  -  | 
              |
| 259 | 
                  -# Fetch (hash)  | 
              |
| 260 | 
                  -sub fetch_hash {
                 | 
              |
| 261 | 
                  - my $self = shift;  | 
              |
| 262 | 
                  - my $sth = $self->sth;  | 
              |
| 263 | 
                  - my $fetch_filter = $self->fetch_filter;  | 
              |
| 264 | 
                  -  | 
              |
| 265 | 
                  - # Fetch  | 
              |
| 266 | 
                  - my $row = $sth->fetchrow_arrayref;  | 
              |
| 267 | 
                  -  | 
              |
| 268 | 
                  - # Cannot fetch  | 
              |
| 269 | 
                  - return unless $row;  | 
              |
| 270 | 
                  -  | 
              |
| 271 | 
                  - # Keys  | 
              |
| 272 | 
                  -    my $keys  = $sth->{NAME_lc};
                 | 
              |
| 273 | 
                  -  | 
              |
| 274 | 
                  - # Filter  | 
              |
| 275 | 
                  -    my $row_hash = {};
                 | 
              |
| 276 | 
                  -    if ($fetch_filter) {
                 | 
              |
| 277 | 
                  -        my $types = $sth->{TYPE};
                 | 
              |
| 278 | 
                  -        for (my $i = 0; $i < @$keys; $i++) {
                 | 
              |
| 279 | 
                  -            $row_hash->{$keys->[$i]} = $fetch_filter->($keys->[$i], $row->[$i],
                 | 
              |
| 280 | 
                  - $types->[$i], $sth, $i);  | 
              |
| 281 | 
                  - }  | 
              |
| 282 | 
                  - }  | 
              |
| 283 | 
                  -  | 
              |
| 284 | 
                  - # No filter  | 
              |
| 285 | 
                  -    else {
                 | 
              |
| 286 | 
                  -        for (my $i = 0; $i < @$keys; $i++) {
                 | 
              |
| 287 | 
                  -            $row_hash->{$keys->[$i]} = $row->[$i];
                 | 
              |
| 288 | 
                  - }  | 
              |
| 289 | 
                  - }  | 
              |
| 290 | 
                  - return wantarray ? %$row_hash : $row_hash;  | 
              |
| 291 | 
                  -}  | 
              |
| 292 | 
                  -  | 
              |
| 293 | 
                  -# Fetch all (array)  | 
              |
| 294 | 
                  -sub fetch_all {
                 | 
              |
| 295 | 
                  - my $self = shift;  | 
              |
| 296 | 
                  -  | 
              |
| 297 | 
                  - my $rows = [];  | 
              |
| 298 | 
                  -    while(my @row = $self->fetch) {
                 | 
              |
| 299 | 
                  - push @$rows, [@row];  | 
              |
| 300 | 
                  - }  | 
              |
| 301 | 
                  - return wantarray ? @$rows : $rows;  | 
              |
| 302 | 
                  -}  | 
              |
| 303 | 
                  -  | 
              |
| 304 | 
                  -# Fetch all (hash)  | 
              |
| 305 | 
                  -sub fetch_all_hash {
                 | 
              |
| 306 | 
                  - my $self = shift;  | 
              |
| 307 | 
                  -  | 
              |
| 308 | 
                  - my $rows = [];  | 
              |
| 309 | 
                  -    while(my %row = $self->fetch_hash) {
                 | 
              |
| 310 | 
                  -        push @$rows, {%row};
                 | 
              |
| 311 | 
                  - }  | 
              |
| 312 | 
                  - return wantarray ? @$rows : $rows;  | 
              |
| 313 | 
                  -}  | 
              |
| 314 | 
                  -  | 
              |
| 315 | 
                  -# Finish  | 
              |
| 316 | 
                  -sub finish { shift->sth->finish }
                 | 
              |
| 317 | 
                  -  | 
              |
| 318 | 
                  -# Error  | 
              |
| 319 | 
                  -sub error { 
                 | 
              |
| 320 | 
                  - my $self = shift;  | 
              |
| 321 | 
                  - my $sth = $self->sth;  | 
              |
| 322 | 
                  - return wantarray ? ($sth->errstr, $sth->err, $sth->state) : $sth->errstr;  | 
              |
| 323 | 
                  -}  | 
              |
| 324 | 
                  -  | 
              |
| 325 | 
                  -Object::Simple->build_class;  | 
              |
| 326 | 
                  -  | 
              |
| 327 | 
                  -  | 
              |
| 328 | 
                  -package DBI::Custom::SQL::Template;  | 
              |
| 329 | 
                  -use Object::Simple;  | 
              |
| 330 | 
                  -use Carp 'croak';  | 
              |
| 331 | 
                  -  | 
              |
| 332 | 
                  -# Clone  | 
              |
| 333 | 
                  -sub clone {
                 | 
              |
| 334 | 
                  - my $self = shift;  | 
              |
| 335 | 
                  - my $new = $self->Object::Simple::new;  | 
              |
| 336 | 
                  -  | 
              |
| 337 | 
                  - # Scalar copy  | 
              |
| 338 | 
                  -    foreach my $attr (qw/tag_start tag_end bind_filter upper_case tag_syntax template/) {
                 | 
              |
| 339 | 
                  - $new->$attr($self->$attr);  | 
              |
| 340 | 
                  - }  | 
              |
| 341 | 
                  -  | 
              |
| 342 | 
                  - # Hash ref copy  | 
              |
| 343 | 
                  -    foreach my $attr (qw/tag_processors/) {
                 | 
              |
| 344 | 
                  -        $new->$attr(\%{$self->$attr || {}});
                 | 
              |
| 345 | 
                  - }  | 
              |
| 346 | 
                  -  | 
              |
| 347 | 
                  - # Other  | 
              |
| 348 | 
                  - $new->tree([]);  | 
              |
| 349 | 
                  -  | 
              |
| 350 | 
                  - return $new;  | 
              |
| 351 | 
                  -}  | 
              |
| 352 | 
                  -  | 
              |
| 353 | 
                  -  | 
              |
| 354 | 
                  -### Attributes;  | 
              |
| 355 | 
                  -sub tag_start   : Attr { default => '{' }
                 | 
              |
| 356 | 
                  -sub tag_end     : Attr { default => '}' }
                 | 
              |
| 357 | 
                  -sub template    : Attr {};
                 | 
              |
| 358 | 
                  -sub tree        : Attr { default => sub { [] } }
                 | 
              |
| 359 | 
                  -sub bind_filter : Attr {}
                 | 
              |
| 360 | 
                  -sub upper_case  : Attr {default => 0}
                 | 
              |
| 361 | 
                  -  | 
              |
| 362 | 
                  -sub tag_syntax : Attr { default => <<'EOS' };
                 | 
              |
| 363 | 
                  -{? name}         ?
                 | 
              |
| 364 | 
                  -{= name}         name = ?
                 | 
              |
| 365 | 
                  -{<> name}        name <> ?
                 | 
              |
| 366 | 
                  -  | 
              |
| 367 | 
                  -{< name}         name < ?
                 | 
              |
| 368 | 
                  -{> name}         name > ?
                 | 
              |
| 369 | 
                  -{>= name}        name >= ?
                 | 
              |
| 370 | 
                  -{<= name}        name <= ?
                 | 
              |
| 371 | 
                  -  | 
              |
| 372 | 
                  -{like name}      name like ?
                 | 
              |
| 373 | 
                  -{in name}        name in [?, ?, ..]
                 | 
              |
| 374 | 
                  -  | 
              |
| 375 | 
                  -{insert_values}  (key1, key2, key3) values (?, ?, ?)
                 | 
              |
| 376 | 
                  -{update_values}  set key1 = ?, key2 = ?, key3 = ?
                 | 
              |
| 377 | 
                  -EOS  | 
              |
| 378 | 
                  -  | 
              |
| 379 | 
                  -sub tag_processors : Attr {type => 'hash', deref => 1, auto_build => sub { 
                 | 
              |
| 380 | 
                  - shift->tag_processors(  | 
              |
| 381 | 
                  - '?' => \&DBI::Custom::SQL::Template::TagProcessor::expand_place_holder,  | 
              |
| 382 | 
                  - '=' => \&DBI::Custom::SQL::Template::TagProcessor::expand_place_holder,  | 
              |
| 383 | 
                  - '<>' => \&DBI::Custom::SQL::Template::TagProcessor::expand_place_holder,  | 
              |
| 384 | 
                  - '>' => \&DBI::Custom::SQL::Template::TagProcessor::expand_place_holder,  | 
              |
| 385 | 
                  - '<' => \&DBI::Custom::SQL::Template::TagProcessor::expand_place_holder,  | 
              |
| 386 | 
                  - '>=' => \&DBI::Custom::SQL::Template::TagProcessor::expand_place_holder,  | 
              |
| 387 | 
                  - '<=' => \&DBI::Custom::SQL::Template::TagProcessor::expand_place_holder,  | 
              |
| 388 | 
                  - 'like' => \&DBI::Custom::SQL::Template::TagProcessor::expand_place_holder,  | 
              |
| 389 | 
                  - 'in' => \&DBI::Custom::SQL::Template::TagProcessor::expand_place_holder,  | 
              |
| 390 | 
                  - 'insert_values' => \&DBI::Custom::SQL::Template::TagProcessor::expand_insert_values,  | 
              |
| 391 | 
                  - 'update_set' => \&DBI::Custom::SQL::Template::TagProcessor::expand_update_set  | 
              |
| 392 | 
                  - );  | 
              |
| 393 | 
                  -}}  | 
              |
| 394 | 
                  -  | 
              |
| 395 | 
                  -sub add_tag_processor {
                 | 
              |
| 396 | 
                  - my $class = shift;  | 
              |
| 397 | 
                  -    my $tag_processors = ref $_[0] eq 'HASH' ? $_[0] : {@_};
                 | 
              |
| 398 | 
                  -    $class->tag_processor(%{$class->tag_processor}, %{$tag_processors});
                 | 
              |
| 399 | 
                  -}  | 
              |
| 400 | 
                  -  | 
              |
| 401 | 
                  -sub create_sql {
                 | 
              |
| 402 | 
                  - my ($self, $template, $values, $filter) = @_;  | 
              |
| 403 | 
                  -  | 
              |
| 404 | 
                  - $filter ||= $self->bind_filter;  | 
              |
| 405 | 
                  -  | 
              |
| 406 | 
                  - $self->parse($template);  | 
              |
| 407 | 
                  -  | 
              |
| 408 | 
                  -    my ($sql, @bind) = $self->build_sql({bind_filter => $filter, values => $values});
                 | 
              |
| 409 | 
                  -  | 
              |
| 410 | 
                  - return ($sql, @bind);  | 
              |
| 411 | 
                  -}  | 
              |
| 412 | 
                  -  | 
              |
| 413 | 
                  -sub parse {
                 | 
              |
| 414 | 
                  - my ($self, $template) = @_;  | 
              |
| 415 | 
                  - $self->template($template);  | 
              |
| 416 | 
                  -  | 
              |
| 417 | 
                  - # Clean start;  | 
              |
| 418 | 
                  - $self->tree([]);  | 
              |
| 419 | 
                  -  | 
              |
| 420 | 
                  - # Tags  | 
              |
| 421 | 
                  - my $tag_start = quotemeta $self->tag_start;  | 
              |
| 422 | 
                  - my $tag_end = quotemeta $self->tag_end;  | 
              |
| 423 | 
                  -  | 
              |
| 424 | 
                  - # Tokenize  | 
              |
| 425 | 
                  - my $state = 'text';  | 
              |
| 426 | 
                  -  | 
              |
| 427 | 
                  - # Save original template  | 
              |
| 428 | 
                  - my $original_template = $template;  | 
              |
| 429 | 
                  -  | 
              |
| 430 | 
                  - # Text  | 
              |
| 431 | 
                  -    while ($template =~ s/([^$tag_start]*?)$tag_start([^$tag_end].*?)$tag_end//sm) {
                 | 
              |
| 432 | 
                  - my $text = $1;  | 
              |
| 433 | 
                  - my $tag = $2;  | 
              |
| 434 | 
                  -  | 
              |
| 435 | 
                  -        push @{$self->tree}, {type => 'text', args => [$text]} if $text;
                 | 
              |
| 436 | 
                  -  | 
              |
| 437 | 
                  -        if ($tag) {
                 | 
              |
| 438 | 
                  -  | 
              |
| 439 | 
                  - my ($tag_name, @args) = split /\s+/, $tag;  | 
              |
| 440 | 
                  -  | 
              |
| 441 | 
                  - $tag ||= '';  | 
              |
| 442 | 
                  -            unless ($self->tag_processors->{$tag_name}) {
                 | 
              |
| 443 | 
                  - my $tag_syntax = $self->tag_syntax;  | 
              |
| 444 | 
                  -                croak("Tag '{$tag}' in SQL template is not exist.\n\n" .
                 | 
              |
| 445 | 
                  - "SQL template tag syntax\n" .  | 
              |
| 446 | 
                  - "$tag_syntax\n\n" .  | 
              |
| 447 | 
                  - "Your SQL template is \n$original_template\n\n");  | 
              |
| 448 | 
                  - }  | 
              |
| 449 | 
                  -  | 
              |
| 450 | 
                  -            push @{$self->tree}, {type => 'tag', tag_name => $tag_name, args => [@args]};
                 | 
              |
| 451 | 
                  - }  | 
              |
| 452 | 
                  - }  | 
              |
| 453 | 
                  -  | 
              |
| 454 | 
                  -    push @{$self->tree}, {type => 'text', args => [$template]} if $template;
                 | 
              |
| 455 | 
                  -}  | 
              |
| 456 | 
                  -  | 
              |
| 457 | 
                  -sub build_sql {
                 | 
              |
| 458 | 
                  - my ($self, $args) = @_;  | 
              |
| 459 | 
                  -  | 
              |
| 460 | 
                  -    my $tree        = $args->{tree} || $self->tree;
                 | 
              |
| 461 | 
                  -    my $bind_filter = $args->{bind_filter} || $self->bind_filter;
                 | 
              |
| 462 | 
                  -    my $values      = $args->{values} || {};
                 | 
              |
| 463 | 
                  -  | 
              |
| 464 | 
                  - my @bind_values_all;  | 
              |
| 465 | 
                  - my $sql = '';  | 
              |
| 466 | 
                  -    foreach my $node (@$tree) {
                 | 
              |
| 467 | 
                  -        my $type     = $node->{type};
                 | 
              |
| 468 | 
                  -        my $tag_name = $node->{tag_name};
                 | 
              |
| 469 | 
                  -        my $args     = $node->{args};
                 | 
              |
| 470 | 
                  -  | 
              |
| 471 | 
                  -        if ($type eq 'text') {
                 | 
              |
| 472 | 
                  - # Join text  | 
              |
| 473 | 
                  - $sql .= $args->[0];  | 
              |
| 474 | 
                  - }  | 
              |
| 475 | 
                  -        elsif ($type eq 'tag') {
                 | 
              |
| 476 | 
                  -            my $tag_processor = $self->tag_processors->{$tag_name};
                 | 
              |
| 477 | 
                  -  | 
              |
| 478 | 
                  -            croak("Tag processor '$type' must be code reference")
                 | 
              |
| 479 | 
                  - unless ref $tag_processor eq 'CODE';  | 
              |
| 480 | 
                  -  | 
              |
| 481 | 
                  - my ($expand, @bind_values)  | 
              |
| 482 | 
                  - = $tag_processor->($tag_name, $args, $values,  | 
              |
| 483 | 
                  - $bind_filter, $self);  | 
              |
| 484 | 
                  -  | 
              |
| 485 | 
                  - $DB::single = 1;  | 
              |
| 486 | 
                  -            unless ($self->_placeholder_count($expand) == @bind_values) {
                 | 
              |
| 487 | 
                  - require Data::Dumper;  | 
              |
| 488 | 
                  -  | 
              |
| 489 | 
                  - my $bind_values_dump  | 
              |
| 490 | 
                  - = Data::Dumper->Dump([\@bind_values], ['*bind_values']);  | 
              |
| 491 | 
                  -  | 
              |
| 492 | 
                  -                croak("Place holder count must be same as bind value count\n" .
                 | 
              |
| 493 | 
                  - "Tag : $tag_name\n" .  | 
              |
| 494 | 
                  - "Expand : $expand\n" .  | 
              |
| 495 | 
                  - "Bind values: $bind_values_dump\n");  | 
              |
| 496 | 
                  - }  | 
              |
| 497 | 
                  - push @bind_values_all, @bind_values;  | 
              |
| 498 | 
                  - $sql .= $expand;  | 
              |
| 499 | 
                  - }  | 
              |
| 500 | 
                  - }  | 
              |
| 501 | 
                  - $sql .= ';' unless $sql =~ /;$/;  | 
              |
| 502 | 
                  - return ($sql, @bind_values_all);  | 
              |
| 503 | 
                  -}  | 
              |
| 504 | 
                  -  | 
              |
| 505 | 
                  -sub _placeholder_count {
                 | 
              |
| 506 | 
                  - my ($self, $expand) = @_;  | 
              |
| 507 | 
                  - $expand ||= '';  | 
              |
| 508 | 
                  -  | 
              |
| 509 | 
                  - my $count = 0;  | 
              |
| 510 | 
                  - my $pos = -1;  | 
              |
| 511 | 
                  -    while (($pos = index($expand, '?', $pos + 1)) != -1) {
                 | 
              |
| 512 | 
                  - $count++;  | 
              |
| 513 | 
                  - }  | 
              |
| 514 | 
                  - return $count;  | 
              |
| 515 | 
                  -}  | 
              |
| 516 | 
                  -  | 
              |
| 517 | 
                  -Object::Simple->build_class;  | 
              |
| 518 | 
                  -  | 
              |
| 519 | 
                  -  | 
              |
| 520 | 
                  -package DBI::Custom::SQL::Template::TagProcessor;  | 
              |
| 521 | 
                  -use strict;  | 
              |
| 522 | 
                  -use warnings;  | 
              |
| 523 | 
                  -  | 
              |
| 524 | 
                  -sub expand_place_holder {
                 | 
              |
| 525 | 
                  - my ($tag_name, $args, $values, $bind_filter, $sql_tmpl_obj) = @_;  | 
              |
| 526 | 
                  -  | 
              |
| 527 | 
                  - my $key = $args->[0];  | 
              |
| 528 | 
                  -  | 
              |
| 529 | 
                  - my @bind_values;  | 
              |
| 530 | 
                  - # Filter Value  | 
              |
| 531 | 
                  -    if ($tag_name eq 'in') {
                 | 
              |
| 532 | 
                  -        $values->{$key} = [$values->{$key}] unless ref $values->{$key} eq 'ARRAY';
                 | 
              |
| 533 | 
                  -        if ($bind_filter) {
                 | 
              |
| 534 | 
                  -            for (my $i = 0; $i < @$values; $i++) {
                 | 
              |
| 535 | 
                  -                push @bind_values, scalar $bind_filter->($key, $values->{$key}->[$i]);
                 | 
              |
| 536 | 
                  - }  | 
              |
| 537 | 
                  - }  | 
              |
| 538 | 
                  -        else {
                 | 
              |
| 539 | 
                  -            for (my $i = 0; $i < @$values; $i++) {
                 | 
              |
| 540 | 
                  -                push @bind_values, $values->{$key}->[$i];
                 | 
              |
| 541 | 
                  - }  | 
              |
| 542 | 
                  - }  | 
              |
| 543 | 
                  - }  | 
              |
| 544 | 
                  -    else {
                 | 
              |
| 545 | 
                  -        if ($bind_filter) {
                 | 
              |
| 546 | 
                  -            push @bind_values, scalar $bind_filter->($key, $values->{$key});
                 | 
              |
| 547 | 
                  - }  | 
              |
| 548 | 
                  -        else {
                 | 
              |
| 549 | 
                  -            push @bind_values, $values->{$key};
                 | 
              |
| 550 | 
                  - }  | 
              |
| 551 | 
                  - }  | 
              |
| 552 | 
                  -  | 
              |
| 553 | 
                  - $tag_name = uc $tag_name if $sql_tmpl_obj->upper_case;  | 
              |
| 554 | 
                  -  | 
              |
| 555 | 
                  - my $expand;  | 
              |
| 556 | 
                  -    if ($tag_name eq '?') {
                 | 
              |
| 557 | 
                  - $expand = '?';  | 
              |
| 558 | 
                  - }  | 
              |
| 559 | 
                  -    elsif ($tag_name eq 'in') {
                 | 
              |
| 560 | 
                  -        $expand = '(';
                 | 
              |
| 561 | 
                  -        for (my $i = 0; $i < @$values; $i++) {
                 | 
              |
| 562 | 
                  - $expand .= '?, ';  | 
              |
| 563 | 
                  - }  | 
              |
| 564 | 
                  - $expand =~ s/, $'//;  | 
              |
| 565 | 
                  - $expand .= ')';  | 
              |
| 566 | 
                  - }  | 
              |
| 567 | 
                  -    else {
                 | 
              |
| 568 | 
                  - $expand = "$key $tag_name ?";  | 
              |
| 569 | 
                  - }  | 
              |
| 570 | 
                  -  | 
              |
| 571 | 
                  - return ($expand, @bind_values);  | 
              |
| 572 | 
                  -}  | 
              |
| 573 | 
                  -  | 
              |
| 574 | 
                  -sub expand_insert_values {
                 | 
              |
| 575 | 
                  - my ($tag_name, $args, $values, $bind_filter, $sql_tmpl_obj) = @_;  | 
              |
| 576 | 
                  -  | 
              |
| 577 | 
                  -    my $insert_keys = '(';
                 | 
              |
| 578 | 
                  -    my $place_holders = '(';
                 | 
              |
| 579 | 
                  -  | 
              |
| 580 | 
                  -    $values = $args->[0] ? $values->{$args->[0]} : $values->{insert_values};
                 | 
              |
| 581 | 
                  -  | 
              |
| 582 | 
                  - my @bind_values;  | 
              |
| 583 | 
                  -    foreach my $key (sort keys %$values) {
                 | 
              |
| 584 | 
                  -        $bind_filter ? push @bind_values, scalar $bind_filter->($key, $values->{$key})
                 | 
              |
| 585 | 
                  -                     : push @bind_values, $values->{$key};
                 | 
              |
| 586 | 
                  -  | 
              |
| 587 | 
                  - $insert_keys .= "$key, ";  | 
              |
| 588 | 
                  - $place_holders .= "?, ";  | 
              |
| 589 | 
                  - }  | 
              |
| 590 | 
                  -  | 
              |
| 591 | 
                  - $insert_keys =~ s/, $//;  | 
              |
| 592 | 
                  - $insert_keys .= ')';  | 
              |
| 593 | 
                  -  | 
              |
| 594 | 
                  - $place_holders =~ s/, $//;  | 
              |
| 595 | 
                  - $place_holders .= ')';  | 
              |
| 596 | 
                  -  | 
              |
| 597 | 
                  - my $expand = $sql_tmpl_obj->upper_case ? "$insert_keys VALUES $place_holders"  | 
              |
| 598 | 
                  - : "$insert_keys values $place_holders";  | 
              |
| 599 | 
                  -  | 
              |
| 600 | 
                  - return ($expand, @bind_values);  | 
              |
| 601 | 
                  -}  | 
              |
| 602 | 
                  -  | 
              |
| 603 | 
                  -sub expand_update_set {
                 | 
              |
| 604 | 
                  - my ($tag_name, $args, $values, $bind_filter, $sql_tmpl_obj) = @_;  | 
              |
| 605 | 
                  -  | 
              |
| 606 | 
                  - my $expand = $sql_tmpl_obj->upper_case ? 'SET ' : 'set ';  | 
              |
| 607 | 
                  -    $values = $args->[0] ? $values->{$args->[0]} : $values->{update_set};
                 | 
              |
| 608 | 
                  -  | 
              |
| 609 | 
                  - my @bind_values;  | 
              |
| 610 | 
                  -    foreach my $key (sort keys %$values) {
                 | 
              |
| 611 | 
                  -        $bind_filter ? push @bind_values, scalar $bind_filter->($key, $values->{$key})
                 | 
              |
| 612 | 
                  -                     : push @bind_values, $values->{$key};
                 | 
              |
| 613 | 
                  -  | 
              |
| 614 | 
                  - $expand .= "$key = ?, ";  | 
              |
| 615 | 
                  - }  | 
              |
| 616 | 
                  - $expand =~ s/, $//;  | 
              |
| 617 | 
                  - return ($expand, @bind_values);  | 
              |
| 618 | 
                  -}  | 
              |
| 619 | 
                  -  | 
              |
| 620 | 
                  -  | 
              |
| 621 | 
                  -package DBI::Custom;  | 
              |
| 622 | 
                  -1;  | 
              |
| 623 | 
                  -  | 
              |
| 624 | 225 | 
                  =head1 NAME  | 
              
| 625 | 226 | 
                   | 
              
| 626 | 227 | 
                  DBI::Custom - Customizable simple DBI  | 
              
| ... | ... | 
                  @@ -4,6 +4,7 @@ use warnings;  | 
              
| 4 | 4 | 
                   | 
              
| 5 | 5 | 
                  use DBI::Custom;  | 
              
| 6 | 6 | 
                  use Scalar::Util qw/blessed/;  | 
              
| 7 | 
                  +use DBI::Custom::SQL::Template;  | 
              |
| 7 | 8 | 
                   | 
              
| 8 | 9 | 
                  my $sql_tmpl1 = DBI::Custom::SQL::Template->new->upper_case(0);  | 
              
| 9 | 10 | 
                  my $sql_tmpl2 = DBI::Custom::SQL::Template->new->upper_case(1);  | 
              
| ... | ... | 
                  @@ -22,14 +23,13 @@ my $sql_tmpl3 = DBI::Custom::SQL::Template->new->upper_case(2);  | 
              
| 22 | 23 | 
                  },  | 
              
| 23 | 24 | 
                  bind_filter => 'f',  | 
              
| 24 | 25 | 
                  fetch_filter => 'g',  | 
              
| 25 | 
                  - dbh => 'e',  | 
              |
| 26 | 26 | 
                  result_class => 'g',  | 
              
| 27 | 27 | 
                  sql_template => $sql_tmpl1,  | 
              
| 28 | 28 | 
                           valid_connect_info => {i => 1}
                 | 
              
| 29 | 29 | 
                  );  | 
              
| 30 | 30 | 
                       is_deeply($dbi,{connect_info => {user => 'a', password => 'b', data_source => 'c', 
                 | 
              
| 31 | 31 | 
                                       options => {d => 1, e => 2}}, filters => {f => 3}, bind_filter => 'f',
                 | 
              
| 32 | 
                  - fetch_filter => 'g', dbh => 'e', result_class => 'g',  | 
              |
| 32 | 
                  + fetch_filter => 'g', result_class => 'g',  | 
              |
| 33 | 33 | 
                                       sql_template => $sql_tmpl1, valid_connect_info => {i => 1}}, 'new');
                 | 
              
| 34 | 34 | 
                   | 
              
| 35 | 35 | 
                  isa_ok($dbi, 'DBI::Custom');  | 
              
| ... | ... | 
                  @@ -40,9 +40,9 @@ my $sql_tmpl3 = DBI::Custom::SQL::Template->new->upper_case(2);  | 
              
| 40 | 40 | 
                  package DBI::Custom::T1;  | 
              
| 41 | 41 | 
                  use base 'DBI::Custom';  | 
              
| 42 | 42 | 
                   | 
              
| 43 | 
                  - my $prototype = __PACKAGE__->prototype;  | 
              |
| 43 | 
                  + my $class = __PACKAGE__;  | 
              |
| 44 | 44 | 
                   | 
              
| 45 | 
                  - $prototype  | 
              |
| 45 | 
                  + $class  | 
              |
| 46 | 46 | 
                  ->connect_info(  | 
              
| 47 | 47 | 
                  user => 'a',  | 
              
| 48 | 48 | 
                  password => 'b',  | 
              
| ... | ... | 
                  @@ -54,7 +54,6 @@ my $sql_tmpl3 = DBI::Custom::SQL::Template->new->upper_case(2);  | 
              
| 54 | 54 | 
                  )  | 
              
| 55 | 55 | 
                         ->bind_filter('f')
                 | 
              
| 56 | 56 | 
                         ->fetch_filter('g')
                 | 
              
| 57 | 
                  -      ->dbh('e')
                 | 
              |
| 58 | 57 | 
                         ->result_class('DBI::Custom::Result')
                 | 
              
| 59 | 58 | 
                  ->sql_template($sql_tmpl1)  | 
              
| 60 | 59 | 
                         ->valid_connect_info({p => 1})
                 | 
              
| ... | ... | 
                  @@ -90,11 +89,13 @@ my $sql_tmpl3 = DBI::Custom::SQL::Template->new->upper_case(2);  | 
              
| 90 | 89 | 
                   {
                 | 
              
| 91 | 90 | 
                  my $dbi = DBI::Custom::T1->new;  | 
              
| 92 | 91 | 
                   | 
              
| 93 | 
                  -    my $sql_tmpl = delete $dbi->{sql_template};
                 | 
              |
| 94 | 
                  -    is_deeply($dbi,{connect_info => {user => 'a', password => 'b', data_source => 'c', options => {d => 1, e => 2}},
                 | 
              |
| 95 | 
                  -                    filters => {f => 3}, bind_filter => 'f', fetch_filter => 'g', result_class => 'DBI::Custom::Result',
                 | 
              |
| 96 | 
                  -                    valid_connect_info => {p => 1}}, 'new custom class');
                 | 
              |
| 97 | 
                  -  | 
              |
| 92 | 
                  +    is_deeply($dbi->connect_info, {user => 'a', password => 'b', data_source => 'c', options => {d => 1, e => 2}});
                 | 
              |
| 93 | 
                  +    is_deeply({$dbi->filters}, {f => 3});
                 | 
              |
| 94 | 
                  + is($dbi->bind_filter, 'f');  | 
              |
| 95 | 
                  + is($dbi->fetch_filter, 'g');  | 
              |
| 96 | 
                  + is($dbi->result_class, 'DBI::Custom::Result');  | 
              |
| 97 | 
                  +    is_deeply({$dbi->valid_connect_info},{p => 1});
                 | 
              |
| 98 | 
                  + is($dbi->sql_template->upper_case, 0);  | 
              |
| 98 | 99 | 
                  isa_ok($dbi, 'DBI::Custom::T1');  | 
              
| 99 | 100 | 
                   | 
              
| 100 | 101 | 
                  }  | 
              
| ... | ... | 
                  @@ -107,11 +108,13 @@ my $sql_tmpl3 = DBI::Custom::SQL::Template->new->upper_case(2);  | 
              
| 107 | 108 | 
                   {
                 | 
              
| 108 | 109 | 
                  my $dbi = DBI::Custom::T1_2->new;  | 
              
| 109 | 110 | 
                   | 
              
| 110 | 
                  -    my $sql_tmpl = delete $dbi->{sql_template};
                 | 
              |
| 111 | 
                  - is($sql_tmpl->upper_case, 0);  | 
              |
| 112 | 
                  -    is_deeply($dbi,{connect_info => {user => 'a', password => 'b', data_source => 'c', options => {d => 1, e => 2}},
                 | 
              |
| 113 | 
                  -                    filters => {f => 3}, bind_filter => 'f', fetch_filter => 'g', result_class => 'DBI::Custom::Result',
                 | 
              |
| 114 | 
                  -                    valid_connect_info => {p => 1}}, 'new custom class inherit');
                 | 
              |
| 111 | 
                  +    is_deeply($dbi->connect_info, {user => 'a', password => 'b', data_source => 'c', options => {d => 1, e => 2}});
                 | 
              |
| 112 | 
                  +    is_deeply(scalar $dbi->filters, {f => 3});
                 | 
              |
| 113 | 
                  + is($dbi->bind_filter, 'f');  | 
              |
| 114 | 
                  + is($dbi->fetch_filter, 'g');  | 
              |
| 115 | 
                  + is($dbi->result_class, 'DBI::Custom::Result');  | 
              |
| 116 | 
                  +    is_deeply({$dbi->valid_connect_info}, {p => 1});
                 | 
              |
| 117 | 
                  + is($dbi->sql_template->upper_case, 0);  | 
              |
| 115 | 118 | 
                   | 
              
| 116 | 119 | 
                  isa_ok($dbi, 'DBI::Custom::T1_2');  | 
              
| 117 | 120 | 
                  }  | 
              
| ... | ... | 
                  @@ -120,9 +123,9 @@ my $sql_tmpl3 = DBI::Custom::SQL::Template->new->upper_case(2);  | 
              
| 120 | 123 | 
                  package DBI::Custom::T1_3;  | 
              
| 121 | 124 | 
                  use base 'DBI::Custom::T1';  | 
              
| 122 | 125 | 
                   | 
              
| 123 | 
                  - my $prototype = __PACKAGE__->prototype;  | 
              |
| 126 | 
                  + my $class = __PACKAGE__;  | 
              |
| 124 | 127 | 
                   | 
              
| 125 | 
                  - $prototype  | 
              |
| 128 | 
                  + $class  | 
              |
| 126 | 129 | 
                  ->connect_info(  | 
              
| 127 | 130 | 
                  user => 'ao',  | 
              
| 128 | 131 | 
                  password => 'bo',  | 
              
| ... | ... | 
                  @@ -134,7 +137,6 @@ my $sql_tmpl3 = DBI::Custom::SQL::Template->new->upper_case(2);  | 
              
| 134 | 137 | 
                  )  | 
              
| 135 | 138 | 
                         ->bind_filter('fo')
                 | 
              
| 136 | 139 | 
                         ->fetch_filter('go')
                 | 
              
| 137 | 
                  -      ->dbh('eo')
                 | 
              |
| 138 | 140 | 
                         ->result_class('ho')
                 | 
              
| 139 | 141 | 
                  ->sql_template($sql_tmpl2)  | 
              
| 140 | 142 | 
                         ->valid_connect_info({p => 3})
                 | 
              
| ... | ... | 
                  @@ -144,11 +146,13 @@ my $sql_tmpl3 = DBI::Custom::SQL::Template->new->upper_case(2);  | 
              
| 144 | 146 | 
                   {
                 | 
              
| 145 | 147 | 
                  my $dbi = DBI::Custom::T1_3->new;  | 
              
| 146 | 148 | 
                   | 
              
| 147 | 
                  -    my $sql_tmpl = delete $dbi->{sql_template};
                 | 
              |
| 148 | 
                  - is($sql_tmpl->upper_case, 1);  | 
              |
| 149 | 
                  -    is_deeply($dbi,{connect_info => {user => 'ao', password => 'bo', data_source => 'co', options => {do => 10, eo => 20}},
                 | 
              |
| 150 | 
                  -                    filters => {fo => 30}, bind_filter => 'fo', fetch_filter => 'go', result_class => 'ho',
                 | 
              |
| 151 | 
                  -                    valid_connect_info => {p => 3}}, 'new custom class');
                 | 
              |
| 149 | 
                  +    is_deeply($dbi->connect_info, {user => 'ao', password => 'bo', data_source => 'co', options => {do => 10, eo => 20}});
                 | 
              |
| 150 | 
                  +    is_deeply(scalar $dbi->filters, {fo => 30});
                 | 
              |
| 151 | 
                  + is($dbi->bind_filter, 'fo');  | 
              |
| 152 | 
                  + is($dbi->fetch_filter, 'go');  | 
              |
| 153 | 
                  + is($dbi->result_class, 'ho');  | 
              |
| 154 | 
                  +    is_deeply(scalar $dbi->valid_connect_info, {p => 3});
                 | 
              |
| 155 | 
                  + is($dbi->sql_template->upper_case, 1);  | 
              |
| 152 | 156 | 
                   | 
              
| 153 | 157 | 
                  isa_ok($dbi, 'DBI::Custom::T1_3');  | 
              
| 154 | 158 | 
                  }  | 
              
| ... | ... | 
                  @@ -166,7 +170,6 @@ my $sql_tmpl3 = DBI::Custom::SQL::Template->new->upper_case(2);  | 
              
| 166 | 170 | 
                  },  | 
              
| 167 | 171 | 
                  bind_filter => 'f',  | 
              
| 168 | 172 | 
                  fetch_filter => 'g',  | 
              
| 169 | 
                  - dbh => 'e',  | 
              |
| 170 | 173 | 
                  result_class => 'h',  | 
              
| 171 | 174 | 
                  sql_template => $sql_tmpl3,  | 
              
| 172 | 175 | 
                           valid_connect_info => {p => 4}
                 | 
              
| ... | ... | 
                  @@ -175,7 +178,7 @@ my $sql_tmpl3 = DBI::Custom::SQL::Template->new->upper_case(2);  | 
              
| 175 | 178 | 
                       my $sql_tmpl = delete $dbi->{sql_template};
                 | 
              
| 176 | 179 | 
                  is($sql_tmpl->upper_case, 2);  | 
              
| 177 | 180 | 
                       is_deeply($dbi,{connect_info => {user => 'a', password => 'b', data_source => 'c', options => {d => 1, e => 2}},
                 | 
              
| 178 | 
                  -                    filters => {f => 3}, bind_filter => 'f', fetch_filter => 'g', dbh => 'e', result_class => 'h',
                 | 
              |
| 181 | 
                  +                    filters => {f => 3}, bind_filter => 'f', fetch_filter => 'g', result_class => 'h',
                 | 
              |
| 179 | 182 | 
                                       valid_connect_info => {p => 4}}, 'new');
                 | 
              
| 180 | 183 | 
                   | 
              
| 181 | 184 | 
                  isa_ok($dbi, 'DBI::Custom');  |