Aniki

 view release on metacpan or  search on metacpan

Changes  view on Meta::CPAN

    - 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

README.md  view on Meta::CPAN

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`

README.md  view on Meta::CPAN

# 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.'_' :
                 '';



( run in 0.776 second using v1.01-cache-2.11-cpan-702932259ff )