From 1606510d18fddc9abf4c95a8237fbf60d6c90a08 Mon Sep 17 00:00:00 2001 From: ytnobody Date: Tue, 7 Jan 2014 18:14:32 +0900 Subject: [PATCH] see DBIx::Sunny line 84 --- cpanfile | 1 + lib/DBIx/Otogiri.pm | 276 ++++++++++++++++++++++++++++++++++++++++++++ lib/Otogiri.pm | 167 +-------------------------- t/11_CLUD.t | 2 +- t/13_irregular.t | 44 +++++++ 5 files changed, 328 insertions(+), 162 deletions(-) create mode 100644 lib/DBIx/Otogiri.pm create mode 100644 t/13_irregular.t diff --git a/cpanfile b/cpanfile index a701588..38981fe 100644 --- a/cpanfile +++ b/cpanfile @@ -5,5 +5,6 @@ requires 'DBIx::Sunny'; on 'test' => sub { requires 'Test::More', '0.98'; + requires 'Test::Exception'; requires 'JSON', '0'; }; diff --git a/lib/DBIx/Otogiri.pm b/lib/DBIx/Otogiri.pm new file mode 100644 index 0000000..60b65a8 --- /dev/null +++ b/lib/DBIx/Otogiri.pm @@ -0,0 +1,276 @@ +package DBIx::Otogiri; +use strict; +use warnings; + +use Class::Accessor::Lite ( + ro => [qw/connect_info/], + rw => [qw/dbh maker/], + new => 0, +); + +use SQL::Maker; +use DBIx::Sunny; + +sub new { + my ($class, %opts) = @_; + my $self = bless {%opts}, $class; + ( $self->{dsn}{scheme}, + $self->{dsn}{driver}, + $self->{dsn}{attr_str}, + $self->{dsn}{attributes}, + $self->{dsn}{driver_dsn} + ) = DBI->parse_dsn($self->{connect_info}[0]); + $self->{dbh} = DBIx::Sunny->connect(@{$self->{connect_info}}); + $self->{maker} = SQL::Maker->new(driver => $self->{dsn}{driver}); + return $self; +} + +sub _deflate_param { + my ($self, $table, $param) = @_; + if ($self->{deflate}) { + $param = $self->{deflate}->({%$param}, $table); + } + return $param; +} + +sub _inflate_rows { + my ($self, $table, @rows) = @_; + @rows = $self->{inflate} ? map {$self->{inflate}->($_, $table)} @rows : @rows; + wantarray ? @rows : $rows[0]; +} + +sub select { + my ($self, $table, $param, @opts) = @_; + $param = $self->_deflate_param($table, $param); + my ($sql, @binds) = $self->maker->select($table, ['*'], $param, @opts); + $self->search_by_sql($sql, @binds); +} + +sub search_by_sql { + my ($self, $sql, @binds) = @_; + my $rtn = $self->dbh->select_all($sql, @binds); + my @rows = $rtn ? $self->_inflate_rows(undef, @$rtn) : (); +} + +sub single { + my ($self, $table, $param, @opts) = @_; + $param = $self->_deflate_param($table, $param); + my ($sql, @binds) = $self->maker->select($table, ['*'], $param, @opts); + my $row = $self->dbh->select_row($sql, @binds); + $self->{inflate} ? $self->_inflate_rows($table, $row) : $row; +} + +sub insert { + my $self = shift; + if ($self->fast_insert(@_)) { + return $self->single(shift, @_); + } +} + +sub fast_insert { + my ($self, $table, $param, @opts) = @_; + $param = $self->_deflate_param($table, $param); + my ($sql, @binds) = $self->maker->insert($table, $param, @opts); + $self->dbh->query($sql, @binds); +} + +sub delete { + my ($self, $table, $param, @opts) = @_; + $param = $self->_deflate_param($table, $param); + my ($sql, @binds) = $self->maker->delete($table, $param, @opts); + $self->dbh->query($sql, @binds); +} + +sub update { + my ($self, $table, $param, @opts) = @_; + $param = $self->_deflate_param($table, $param); + my ($sql, @binds) = $self->maker->update($table, $param, @opts); + $self->dbh->query($sql, @binds); +} + +sub do { + my $self = shift; + $self->dbh->query(@_); +} + +sub txn_scope { + my $self = shift; + $self->dbh->txn_scope; +} + +sub last_insert_id { + my $self = shift; + $self->dbh->last_insert_id; +} + +1; +__END__ + +=encoding utf-8 + +=head1 NAME + +DBIx::Otogiri - Core of Otogiri + +=head1 SYNOPSIS + + use Otogiri; + my $db = Otogiri->new(connect_info => ['dbi:SQLite:...', '', '']); + + my $row = $db->insert(book => {title => 'mybook1', author => 'me', ...}); + print 'Title: '. $row->{title}. "\n"; + + my @rows = $db->select(book => {price => {'>=' => 500}}); + for my $r (@rows) { + printf "Title: %s \nPrice: %s yen\n", $r->{title}, $r->{price}; + } + + $db->update(book => [author => 'oreore'], {author => 'me'}); + + $db->delete(book => {author => 'me'}); + + ### insert without row-data in response + $db->fast_insert(book => {title => 'someone', ...}); + + ### using transaction + do { + my $txn = $db->txn_scope; + $db->insert(book => ...); + $db->insert(store => ...); + $txn->commit; + }; + +=head1 DESCRIPTION + +Core feature of L + +=head1 ATTRIBUTE + +=head2 connect_info (required) + + connect_info => [$dsn, $dbuser, $dbpass], + +You have to specify dsn, dbuser, and dbpass, to connect to database. + +=head2 inflate (optional) + + use JSON; + inflate => sub { + my ($data, $tablename) = @_; + if (defined $data->{json}) { + $data->{json} = decode_json($data->{json}); + } + $data->{table} = $tablename; + $data; + }, + +You may specify column inflation logic. + +Specified code is called internally when called select(), search_by_sql(), and single(). + +=head2 deflate (optional) + + use JSON; + deflate => sub { + my ($data, $tablename) = @_; + if (defined $data->{json}) { + $data->{json} = encode_json($data->{json}); + } + delete $data->{table}; + $data; + }, + +You may specify column deflation logic. + +Specified code is called internally when called insert(), update(), and delete(). + +=head1 METHODS + +=head2 new + + my $db = Otogiri->new( connect_info => [$dsn, $dbuser, $dbpass] ); + +Instantiate and connect to db. + +Please see ATTRIBUTE section. + +=head2 insert + + my $row = $db->insert($table_name => $columns_in_hashref); + +Insert data. Then, returns row data. + +=head2 fast_insert + + $db->fast_insert($table_name => $columns_in_hashref); + +Insert data simply. + +=head2 select + + my @rows = $db->select($table_name => $conditions_in_hashref [,@options]); + +Select from specified table. Then, returns matched rows as array. + +=head2 single + + my $row = $db->single($table_name => $conditions_in_hashref [,@options]); + +Select from specified table. Then, returns first of matched rows. + +=head2 search_by_sql + + my @rows = $db->search_by_sql($sql, @bind_vals); + +Select by specified SQL. Then, returns matched rows as array. + +=head2 update + + $db->update($table_name => [update_col_1 => $new_value_1, ...], $conditions_in_hashref); + +Update rows that matched to $conditions_in_hashref. + +=head2 delete + + $db->delete($table_name => $conditions_in_hashref); + +Delete rows that matched to $conditions_in_hashref. + +=head2 do + + $db->do($sql, @bind_vals); + +Execute specified SQL. + +=head2 txn_scope + + my $txn = $db->txn_scope; + +returns DBIx::TransactionManager::ScopeGuard's instance. See L to more information. + +=head2 last_insert_id + + my $id = $db->last_insert_id([@args]); + +returns last_insert_id. (mysql_insertid in MySQL or last_insert_rowid in SQLite) + + +=head1 LICENSE + +Copyright (C) ytnobody. + +This library is free software; you can redistribute it and/or modify +it under the same terms as Perl itself. + +=head1 AUTHOR + +ytnobody Eytnobody@gmail.comE + +=head1 SEE ALSO + +L + +L + +=cut + diff --git a/lib/Otogiri.pm b/lib/Otogiri.pm index 1ac6590..b78297c 100644 --- a/lib/Otogiri.pm +++ b/lib/Otogiri.pm @@ -5,105 +5,11 @@ use warnings; our $VERSION = "0.02"; -use Class::Accessor::Lite ( - ro => [qw/connect_info/], - rw => [qw/dbh maker/], - new => 0, -); - -use SQL::Maker; -use DBIx::Sunny; +use DBIx::Otogiri; sub new { my ($class, %opts) = @_; - my $self = bless {%opts}, $class; - ( $self->{dsn}{scheme}, - $self->{dsn}{driver}, - $self->{dsn}{attr_str}, - $self->{dsn}{attributes}, - $self->{dsn}{driver_dsn} - ) = DBI->parse_dsn($self->{connect_info}[0]); - $self->{dbh} = DBIx::Sunny->connect(@{$self->{connect_info}}); - $self->{maker} = SQL::Maker->new(driver => $self->{dsn}{driver}); - return $self; -} - -sub _deflate_param { - my ($self, $table, $param) = @_; - if ($self->{deflate}) { - $param = $self->{deflate}->({%$param}, $table); - } - return $param; -} - -sub _inflate_rows { - my ($self, $table, @rows) = @_; - @rows = $self->{inflate} ? map {$self->{inflate}->($_, $table)} @rows : @rows; - wantarray ? @rows : $rows[0]; -} - -sub select { - my ($self, $table, $param, @opts) = @_; - $param = $self->_deflate_param($table, $param); - my ($sql, @binds) = $self->maker->select($table, ['*'], $param, @opts); - $self->search_by_sql($sql, @binds); -} - -sub search_by_sql { - my ($self, $sql, @binds) = @_; - my $rtn = $self->dbh->select_all($sql, @binds); - my @rows = $rtn ? $self->_inflate_rows(undef, @$rtn) : (); -} - -sub single { - my ($self, $table, $param, @opts) = @_; - $param = $self->_deflate_param($table, $param); - my ($sql, @binds) = $self->maker->select($table, ['*'], $param, @opts); - my $row = $self->dbh->select_row($sql, @binds); - $self->{inflate} ? $self->_inflate_rows($table, $row) : $row; -} - -sub insert { - my $self = shift; - if ($self->fast_insert(@_)) { - return $self->single(shift, @_); - } -} - -sub fast_insert { - my ($self, $table, $param, @opts) = @_; - $param = $self->_deflate_param($table, $param); - my ($sql, @binds) = $self->maker->insert($table, $param, @opts); - $self->dbh->query($sql, @binds); -} - -sub delete { - my ($self, $table, $param, @opts) = @_; - $param = $self->_deflate_param($table, $param); - my ($sql, @binds) = $self->maker->delete($table, $param, @opts); - $self->dbh->query($sql, @binds); -} - -sub update { - my ($self, $table, $param, @opts) = @_; - $param = $self->_deflate_param($table, $param); - my ($sql, @binds) = $self->maker->update($table, $param, @opts); - $self->dbh->query($sql, @binds); -} - -sub do { - my $self = shift; - $self->dbh->query(@_); -} - -sub txn_scope { - my $self = shift; - $self->dbh->txn_scope; -} - -sub last_insert_id { - my $self = shift; - $self->dbh->last_insert_id; + DBIx::Otogiri->new(%opts); } 1; @@ -145,7 +51,7 @@ Otogiri - A lightweight medicine for using database =head1 DESCRIPTION -Otogiri is one of ORM. A slogan is "Schema-less, Fat-less". +Otogiri is a thing that like as ORM. A slogan is "Schema-less, Fat-less". =head1 ATTRIBUTE @@ -193,70 +99,7 @@ Specified code is called internally when called insert(), update(), and delete() my $db = Otogiri->new( connect_info => [$dsn, $dbuser, $dbpass] ); -Instantiate and connect to db. - -Please see ATTRIBUTE section. - -=head2 insert - - my $row = $db->insert($table_name => $columns_in_hashref); - -Insert data. Then, returns row data. - -=head2 fast_insert - - $db->fast_insert($table_name => $columns_in_hashref); - -Insert data simply. - -=head2 select - - my @rows = $db->select($table_name => $conditions_in_hashref [,@options]); - -Select from specified table. Then, returns matched rows as array. - -=head2 single - - my $row = $db->single($table_name => $conditions_in_hashref [,@options]); - -Select from specified table. Then, returns first of matched rows. - -=head2 search_by_sql - - my @rows = $db->search_by_sql($sql, @bind_vals); - -Select by specified SQL. Then, returns matched rows as array. - -=head2 update - - $db->update($table_name => [update_col_1 => $new_value_1, ...], $conditions_in_hashref); - -Update rows that matched to $conditions_in_hashref. - -=head2 delete - - $db->delete($table_name => $conditions_in_hashref); - -Delete rows that matched to $conditions_in_hashref. - -=head2 do - - $db->do($sql, @bind_vals); - -Execute specified SQL. - -=head2 txn_scope - - my $txn = $db->txn_scope; - -returns DBIx::TransactionManager::ScopeGuard's instance. See L to more information. - -=head2 last_insert_id - - my $id = $db->last_insert_id([@args]); - -returns last_insert_id. (mysql_insertid in MySQL or last_insert_rowid in SQLite) - +Instantiate and connect to db. Then, it returns L object. =head1 LICENSE @@ -271,6 +114,8 @@ ytnobody Eytnobody@gmail.comE =head1 SEE ALSO +L + L L diff --git a/t/11_CLUD.t b/t/11_CLUD.t index 83584f6..9869159 100644 --- a/t/11_CLUD.t +++ b/t/11_CLUD.t @@ -6,7 +6,7 @@ use Otogiri; my $dbfile = ':memory:'; my $db = Otogiri->new( connect_info => ["dbi:SQLite:dbname=$dbfile", '', ''] ); -isa_ok $db, 'Otogiri'; +isa_ok $db, 'DBIx::Otogiri'; can_ok $db, qw/insert fast_insert select single search_by_sql delete update txn_scope dbh maker do/; my $sql = <<'EOF'; diff --git a/t/13_irregular.t b/t/13_irregular.t new file mode 100644 index 0000000..b797029 --- /dev/null +++ b/t/13_irregular.t @@ -0,0 +1,44 @@ +use strict; +use warnings; +use Test::More; +use Test::Exception; +use Otogiri; + +my $dbfile = ':memory:'; + +my $db = Otogiri->new( connect_info => ["dbi:SQLite:dbname=$dbfile", '', ''] ); + +my $sql = <<'EOF'; +CREATE TABLE member ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + name TEXT NOT NULL, + age INTEGER NOT NULL DEFAULT 20, + sex TEXT NOT NULL, + created_at INTEGER NOT NULL, + updated_at INTEGER +); +EOF +$db->do($sql); + +my $time = time; +my $param = { + name => 'ytnobody', + age => 30, + sex => 'male', + created_at => $time, +}; +my $member = $db->insert(member => $param); + +subtest broken_query => sub { + dies_ok { + $db->search_by_sql( + 'SELECT * FROM membre WHERE id = ?', + [ $member->{id} ], + 'member' + ); + } 'select query to non exists table'; + my $filename = __FILE__; + like $@, qr|$filename|, 'check filename that contains into comment in SQL'; +}; + +done_testing;