package DBI::Custom; use Object::Simple; our $VERSION = '0.0101'; use Carp 'croak'; use DBI; use DBI::Custom::SQL::Template; use DBI::Custom::Result; ### Class-Object Accessors sub connect_info : ClassObjectAttr { type => 'hash', auto_build => sub { shift->Object::Simple::initialize_class_object_attr( clone => sub { my $value = shift; my $new_value = \%{$value || {}}; $new_value->{options} = $value->{options} if $value->{options}; return $new_value; }, default => sub { {} }, ) }} sub bind_filter : ClassObjectAttr { auto_build => sub { shift->Object::Simple::initialize_class_object_attr(clone => 'scalar') }} sub fetch_filter : ClassObjectAttr { auto_build => sub { shift->Object::Simple::initialize_class_object_attr(clone => 'scalar') }} sub filters : ClassObjectAttr { type => 'hash', deref => 1, auto_build => sub { shift->Object::Simple::initialize_class_object_attr( clone => 'hash', default => sub { {} } ) }} sub result_class : ClassObjectAttr { auto_build => sub { shift->Object::Simple::initialize_class_object_attr( clone => 'scalar', default => 'DBI::Custom::Result' ) }} sub sql_template : ClassObjectAttr { auto_build => sub { shift->Object::Simple::initialize_class_object_attr( clone => sub {my $value = shift; $value ? $value->clone : undef}, default => sub { DBI::Custom::SQL::Template->new } ) }} sub valid_connect_info : ClassObjectAttr { type => 'hash', deref => 1, auto_build => sub { shift->Object::Simple::initialize_class_object_attr( clone => 'hash', default => sub { return {map {$_ => 1} qw/data_source user password options/} }, ) }} ### Object Accessor sub dbh : Attr {} ### Methods # Add filter sub add_filter { my $invocant = shift; my %old_filters = $invocant->filters; my %new_filters = ref $_[0] eq 'HASH' ? %{$_[0]} : @_; $invocant->filters(%old_filters, %new_filters); } # Auto commit sub auto_commit { my $self = shift; croak("Cannot change AutoCommit becouse of not connected") unless $self->dbh; if (@_) { $self->dbh->{AutoCommit} = $_[0]; return $self; } return $self->dbh->{AutoCommit}; } # Connect sub connect { my $self = shift; my $connect_info = $self->connect_info; foreach my $key (keys %{$self->connect_info}) { croak("connect_info '$key' is wrong name") unless $self->valid_connect_info->{$key}; } my $dbh = DBI->connect( $connect_info->{data_source}, $connect_info->{user}, $connect_info->{password}, { RaiseError => 1, PrintError => 0, AutoCommit => 1, %{$connect_info->{options} || {} } } ); $self->dbh($dbh); return $self; } # DESTROY sub DESTROY { my $self = shift; $self->disconnect if $self->connected; } # Is connected? sub connected { my $self = shift; return exists $self->{dbh} && eval {$self->{dbh}->can('prepare')}; } # Disconnect sub disconnect { my $self = shift; if ($self->connected) { $self->dbh->disconnect; delete $self->{dbh}; } } # Reconnect sub reconnect { my $self = shift; $self->disconnect if $self->connected; $self->connect; } # Run tranzaction sub run_tranzaction { my ($self, $tranzaction) = @_; $self->auto_commit(0); eval { $tranzaction->(); $self->dbh->commit; }; if ($@) { my $tranzaction_error = $@; $self->dbh->rollback or croak("$@ and rollback also failed"); croak("$tranzaction_error"); } $self->auto_commit(1); } # Create SQL from SQL template sub create_sql { my $self = shift; my ($sql, @bind) = $self->sql_template->create_sql(@_); return ($sql, @bind); } # Prepare and execute SQL sub query { my ($self, $template, $values, $filter) = @_; my $sth_options; # Rearrange when argumets is hash referecne if (ref $template eq 'HASH') { my $args = $template; ($template, $values, $filter, $sth_options) = @{$args}{qw/template values filter sth_options/}; } $filter ||= $self->bind_filter; my ($sql, @bind) = $self->create_sql($template, $values, $filter); $self->connect unless $self->connected; my $sth = $self->dbh->prepare($sql); if ($sth_options) { foreach my $key (keys %$sth_options) { $sth->{$key} = $sth_options->{$key}; } } # Execute my $ret_val = $sth->execute(@bind); # Return resultset if select statement is executed if ($sth->{NUM_OF_FIELDS}) { my $result_class = $self->result_class; my $result = $result_class->new({ sth => $sth, fetch_filter => $self->fetch_filter }); return $result; } return $ret_val; } # Prepare and execute raw SQL sub query_raw_sql { my ($self, $sql, @bind_values) = @_; # Connect $self->connect unless $self->connected; # Add semicolon if not exist; $sql .= ';' unless $sql =~ /;$/; # Prepare my $sth = $self->dbh->prepare($sql); # Execute $sth->execute(@bind_values); return $sth; } Object::Simple->build_class; =head1 NAME DBI::Custom - Customizable simple DBI =head1 VERSION Version 0.0101 =cut =head1 SYNOPSIS my $dbi = DBI::Custom->new; =head1 METHODS =head2 add_filter =head2 bind_filter =head2 clone =head2 connect =head2 connect_info =head2 dbh =head2 fetch_filter =head2 filters =head2 prototype =head2 new =head2 query =head2 create_sql =head2 query_raw_sql =head2 sql_template =head2 auto_commit =head2 connected =head2 disconnect =head2 reconnect =head2 result_class =head2 run_tranzaction =head2 valid_connect_info =head1 AUTHOR Yuki Kimoto, C<< >> =head1 COPYRIGHT & LICENSE Copyright 2009 Yuki Kimoto, all rights reserved. This program is free software; you can redistribute it and/or modify it under the same terms as Perl itself. =cut 1; # End of DBI::Custom