view release on metacpan or search on metacpan
- Added `count` to Collection.
- Added `is_new` flag to Row class.
- Added `refetch` method to Row class.
- Added shortcut to `txn_manger`.
- Added plugin for paging.
- Added plugin for `SELECT COUNT(...)`.
- Added some tests.
0.01_03 2015-02-09T00:14:31Z
- Fixed guessing relationship name algorithm. (when defined multiple foreign key.)
0.01_02 2015-02-02T09:51:41Z
- Fixed guessing relationship name algorithm.
- Fixed dependencies
0.01_01 2015-01-22T10:30:31Z
- original version
SEE ALSO: [The JSON SQL Injection Vulnerability](http://blog.kazuhooku.com/2014/07/the-json-sql-injection-vulnerability.html)
### `preload_all_row_classes`
Preload all row classes.
### `preload_all_result_classes`
Preload all result classes.
### `guess_result_class($table_name) : ClassName`
Guesses result class by table name.
### `guess_row_class($table_name) : ClassName`
Guesses row class by table name.
### `new(%args) : Aniki`
Create instance of Aniki.
#### Arguments
- `handler : Aniki::Handler`
# stmt: SELECT FROM foo WHERE id = ? LIMIT 1
# bind: [1]
```
#### Options
You can use there options:
- `table_name: Str`
This is table name using row/result class guessing.
- `columns: ArrayRef[Str]`
List for retrieving columns from database.
- `prefetch: ArrayRef|HashRef`
Pre-fetch specified related rows.
See also ["RELATIONSHIP"](#relationship) section.
lib/Aniki.pm view on Meta::CPAN
DB2 => 'DB2',
};
return $map->{$database};
}
sub schema { croak 'This is abstract method. (required to call setup method before call it)' }
sub query_builder { croak 'This is abstract method. (required to call setup method before call it)' }
sub filter { croak 'This is abstract method. (required to call setup method before call it)' }
sub last_insert_id { croak 'This is abstract method. (required to call setup method before call it)' }
sub root_row_class { croak 'This is abstract method. (required to call setup method before call it)' }
sub guess_row_class { croak 'This is abstract method. (required to call setup method before call it)' }
sub root_result_class { croak 'This is abstract method. (required to call setup method before call it)' }
sub guess_result_class { croak 'This is abstract method. (required to call setup method before call it)' }
sub handler_class { 'Aniki::Handler' }
# You can override this method on your application.
sub use_prepare_cached { 1 }
sub use_strict_query_builder { 1 }
sub setup {
my ($class, %args) = @_;
# schema
lib/Aniki.pm view on Meta::CPAN
$query_builder_class = $args{query_builder};
}
my $driver = $class->_database2driver($class->schema->database);
my $query_builder = $query_builder_class->new(driver => $driver, strict => $class->use_strict_query_builder);
$class->meta->add_method(query_builder => sub { $query_builder });
}
# row
{
my $root_row_class = 'Aniki::Row';
my $guess_row_class = sub { $root_row_class };
if ($args{row}) {
Module::Load::load($args{row}) unless Class::Inspector->loaded($args{row});
$root_row_class = $args{row};
my %table_row_class;
$guess_row_class = sub {
my $table_name = $_[1];
return $table_row_class{$table_name} //= try {
my $table_row_class = sprintf '%s::%s', $root_row_class, camelize($table_name);
Module::Load::load($table_row_class);
return $table_row_class;
} catch {
die $_ unless /\A\QCan't locate/imo;
return $root_row_class;
};
};
}
$class->meta->add_method(root_row_class => sub { $root_row_class });
$class->meta->add_method(guess_row_class => $guess_row_class);
}
# result
{
my $root_result_class = 'Aniki::Result::Collection';
my $guess_result_class = sub { $root_result_class };
if ($args{result}) {
Module::Load::load($args{result}) unless Class::Inspector->loaded($args{result});
$root_result_class = $args{result};
my %table_result_class;
$guess_result_class = sub {
my $table_name = $_[1];
return $table_result_class{$table_name} //= try {
my $table_result_class = sprintf '%s::%s', $root_result_class, camelize($table_name);
Module::Load::load($table_result_class);
return $table_result_class;
} catch {
die $_ unless /\A\QCan't locate/imo;
return $root_result_class;
};
};
}
$class->meta->add_method(root_result_class => sub { $root_result_class });
$class->meta->add_method(guess_result_class => $guess_result_class);
}
}
sub preload_all_row_classes {
my $class = shift;
for my $table ($class->schema->get_tables) {
$class->guess_row_class($table->name);
}
}
sub preload_all_result_classes {
my $class = shift;
for my $table ($class->schema->get_tables) {
$class->guess_result_class($table->name);
}
}
sub dbh {
my $self = shift;
# (for mysql)
# XXX: `DBIx::Handler#dbh` send a ping to mysql.
# But, It removes `$dbh->{mysql_insertid}`.
return $self->{_context} if exists $self->{_context};
return $self->handler->dbh;
lib/Aniki.pm view on Meta::CPAN
return $self->select($row->table_name, $where, { limit => 1, suppress_result_objects => 1 })->[0];
}
sub update_and_emulate_row {
my ($self, $row, $set) = @_;
croak '(Aniki#update_and_emulate_row) condition must be a Aniki::Row object.' unless blessed $row && $row->isa('Aniki::Row');
my $emulated_row_data = $self->_update_and_emulate_row_data($row, $set);
return $emulated_row_data if $self->suppress_row_objects;
return $self->guess_row_class($row->table_name)->new(
table_name => $row->table_name,
handler => $self,
row_data => $emulated_row_data,
);
}
sub _update_and_emulate_row_data {
my ($self, $row, $set) = @_;
$set = $self->filter_on_update($row->table_name, $set);
$self->update($row, $set, { no_filter => 1 });
lib/Aniki.pm view on Meta::CPAN
$row_data{$field->name} = ref $default_value eq 'SCALAR' ? undef : $default_value;
}
elsif ($field->is_auto_increment) {
$row_data{$field->name} = $self->last_insert_id($table_name, $field->name);
}
else {
$row_data{$field->name} = undef;
}
}
return \%row_data if $self->suppress_row_objects;
return $self->guess_row_class($table_name)->new(
table_name => $table_name,
handler => $self,
row_data => \%row_data,
is_new => 1,
);
}
sub insert_on_duplicate {
my ($self, $table_name, $insert, $update) = @_;
if ($self->schema->database ne 'MySQL') {
lib/Aniki.pm view on Meta::CPAN
return $self->select_by_sql(bind_named($sql, $bind), $opt);
}
sub select_by_sql {
my ($self, $sql, $bind, $opt) = @_;
$opt //= {};
local $self->{suppress_row_objects} = 1 if $opt->{suppress_row_objects};
local $self->{suppress_result_objects} = 1 if $opt->{suppress_result_objects};
my $table_name = exists $opt->{table_name} ? $opt->{table_name} : $self->_guess_table_name($sql);
my $columns = exists $opt->{columns} ? $opt->{columns} : undef;
my $prefetch = exists $opt->{prefetch} ? $opt->{prefetch} : [];
$prefetch = [$prefetch] if ref $prefetch eq 'HASH';
my $prefetch_enabled_fg = @$prefetch && !$self->suppress_row_objects && defined wantarray;
if ($prefetch_enabled_fg) {
my $txn; $txn = $self->txn_scope(caller => [caller]) unless $self->in_txn;
my $sth = $self->execute($sql, @$bind);
my $result = $self->_fetch_by_sth($sth, $table_name, $columns);
lib/Aniki.pm view on Meta::CPAN
my @rows;
my %row;
$sth->bind_columns(\@row{@$columns});
push @rows => {%row} while $sth->fetch;
$sth->finish;
if ($self->suppress_result_objects) {
return \@rows if $self->suppress_row_objects;
my $row_class = $self->guess_row_class($table_name);
return [
map {
$row_class->new(
table_name => $table_name,
handler => $self,
row_data => $_,
)
} @rows
];
}
my $result_class = $self->guess_result_class($table_name);
return $result_class->new(
table_name => $table_name,
handler => $self,
row_datas => \@rows,
suppress_row_objects => $self->suppress_row_objects,
);
}
sub execute {
my ($self, $sql, @bind) = @_;
lib/Aniki.pm view on Meta::CPAN
my $self = shift;
my %cache = map { $_->name => undef } $self->schema->get_tables();
return \%cache;
},
);
sub new_row_from_hashref {
my ($self, $table_name, $row_data) = @_;
return $row_data if $self->suppress_row_objects;
my $row_class = $self->guess_row_class($table_name);
return $row_class->new(
table_name => $table_name,
handler => $self,
row_data => $row_data,
);
}
sub new_collection_from_arrayref {
my ($self, $table_name, $row_datas) = @_;
return $row_datas if $self->suppress_result_objects;
my $result_class = $self->guess_result_class($table_name);
return $result_class->new(
table_name => $table_name,
handler => $self,
row_datas => $row_datas,
suppress_row_objects => $self->suppress_row_objects,
);
}
sub _guess_table_name {
my ($self, $sql) = @_;
return $2 if $sql =~ /\sfrom\s+(["`]?)([\w]+)\1\s*/sio;
return;
}
# --------------------------------------------------
# last_insert_id
sub _fetch_last_insert_id_from_mysql { shift->dbh->{mysql_insertid} }
sub _fetch_last_insert_id_from_pg {
my ($self, $table_name, $column) = @_;
lib/Aniki.pm view on Meta::CPAN
SEE ALSO: L<The JSON SQL Injection Vulnerability|http://blog.kazuhooku.com/2014/07/the-json-sql-injection-vulnerability.html>
=head3 C<preload_all_row_classes>
Preload all row classes.
=head3 C<preload_all_result_classes>
Preload all result classes.
=head3 C<guess_result_class($table_name) : ClassName>
Guesses result class by table name.
=head3 C<guess_row_class($table_name) : ClassName>
Guesses row class by table name.
=head3 C<new(%args) : Aniki>
Create instance of Aniki.
=head4 Arguments
=over 4
lib/Aniki.pm view on Meta::CPAN
# bind: [1]
=head4 Options
You can use there options:
=over 4
=item C<table_name: Str>
This is table name using row/result class guessing.
=item C<columns: ArrayRef[Str]>
List for retrieving columns from database.
=item C<prefetch: ArrayRef|HashRef>
Pre-fetch specified related rows.
See also L</"RELATIONSHIP"> section.
lib/Aniki/Plugin/PagerInjector.pm view on Meta::CPAN
package Aniki::Plugin::PagerInjector;
use 5.014002;
use namespace::autoclean;
use Mouse::Role;
use Data::Page::NoTotalEntries;
use Aniki::Result::Role::Pager;
requires qw/guess_result_class/;
sub inject_pager_to_result {
my ($self, $result, $opt) = @_;
my $table_name = $result->table_name;
my $has_next = $opt->{rows} < $result->count;
if ($has_next) {
my $result_class = ref $result;
$result = $result_class->new(
table_name => $table_name,
lib/Aniki/Result.pm view on Meta::CPAN
is => 'rw',
lazy => 1,
default => sub { shift->handler->suppress_row_objects },
);
has row_class => (
is => 'rw',
lazy => 1,
default => sub {
my $self = shift;
$self->handler->guess_row_class($self->table_name);
},
);
my %handler;
sub BUILD {
my ($self, $args) = @_;
$handler{0+$self} = delete $args->{handler};
}
lib/Aniki/Result/Collection/Joined.pm view on Meta::CPAN
default => sub {
my $self = shift;
return +{
map { $_ => undef } @{ $self->table_names },
};
},
);
sub row_classes {
my $self = shift;
return map { $self->handler->guess_row_class($_) } @{ $self->table_names };
}
sub rows {
my $self = shift;
if (@_ == 1) {
my $table_name = shift;
return $self->subresult($table_name)->rows();
}
return $self->SUPER::rows();
}
sub subresult {
my ($self, $table_name) = @_;
return $self->_subresult_cache->{$table_name} if $self->_subresult_cache->{$table_name};
my $result_class = $self->handler->guess_result_class($table_name);
return $self->_subresult_cache->{$table_name} = $result_class->new(
table_name => $table_name,
handler => $self->handler,
row_datas => [uniq_by { refaddr $_ } map { $_->{$table_name} } @{ $self->_compact_row_datas() }],
!$self->suppress_row_objects ? (
inflated_rows => [uniq_by { refaddr $_ } map { $_->$table_name } @{ $self->inflated_rows() }],
) : (),
suppress_row_objects => $self->suppress_row_objects,
);
}
lib/Aniki/Result/Collection/Joined.pm view on Meta::CPAN
}
return \@rows;
}
sub _inflate {
my $self = shift;
my $handler = $self->handler;
my @table_names = @{ $self->table_names };
my %row_class = map { $_ => $handler->guess_row_class($_) } @table_names;
my @rows;
my %cache;
for my $row (@{ $self->_compact_row_datas }) {
my %rows;
# inflate to row class
for my $table_name (@table_names) {
my $row_data = $row->{$table_name};
$rows{$table_name} = $cache{$table_name}{refaddr $row_data} //= $row_class{$table_name}->new(
lib/Aniki/Row.pm view on Meta::CPAN
my $where = $self->handler->_where_row_cond($self->table, $self->row_data);
return $self->handler->select($self->table_name => $where, $opts)->first;
}
my %accessor_method_cache;
sub _accessor_method_cache {
my $self = shift;
return $accessor_method_cache{$self->table_name} //= {};
}
sub _guess_accessor_method {
my ($invocant, $method) = @_;
if (ref $invocant) {
my $self = $invocant;
my $column = $method;
my $cache = $self->_accessor_method_cache();
return $cache->{$column} if exists $cache->{$column};
return $cache->{$column} = sub { shift->get($column) } if exists $self->row_data->{$column};
lib/Aniki/Row.pm view on Meta::CPAN
return $cache->{$column} = sub { shift->relay($column) } if $relationships && $relationships->get($column);
}
return undef; ## no critic
}
sub can {
my ($invocant, $method) = @_;
my $code = $invocant->SUPER::can($method);
return $code if defined $code;
return $invocant->_guess_accessor_method($method);
}
our $AUTOLOAD;
sub AUTOLOAD {
my $invocant = shift;
my $column = $AUTOLOAD =~ s/^.+://r;
if (ref $invocant) {
my $self = $invocant;
my $method = $self->_guess_accessor_method($column);
return $self->$method(@_) if defined $method;
}
my $msg = sprintf q{Can't locate object method "%s" via package "%s"}, $column, ref $invocant || $invocant;
croak $msg;
}
sub DEMOLISH {
my $self = shift;
delete $handler{0+$self};
lib/Aniki/Schema/Relationship.pm view on Meta::CPAN
has has_many => (
is => 'ro',
default => sub {
my $self = shift;
return $self->schema->has_many($self->dest_table_name, $self->dest_columns);
},
);
has name => (
is => 'ro',
default => \&_guess_name,
);
has fetcher => (
is => 'ro',
default => sub { Aniki::Schema::Relationship::Fetcher->new(relationship => $_[0]) },
);
sub _guess_name {
my $self = shift;
my @src_columns = @{ $self->src_columns };
my @dest_columns = @{ $self->dest_columns };
my $src_table_name = $self->src_table_name;
my $dest_table_name = $self->dest_table_name;
my $prefix = (@src_columns == 1 && $src_columns[0] =~ /^(.+)_\Q$dest_table_name/) ? $1.'_' :
(@dest_columns == 1 && $dest_columns[0] =~ /^(.+)_\Q$src_table_name/) ? $1.'_' :
'';