Aniki

 view release on metacpan or  search on metacpan

Changes  view on Meta::CPAN

0.89 2016-02-21T03:17:18Z

   - fixed pod syntax

0.88 2016-02-19T08:42:25Z

   - [BUGFIX] fixed `last_insert_id` on MySQL. (`DBIx::Handler#dbh` send a ping to mysql. But, It removes `$dbh->{mysql_insertid}`.)

0.87 2016-02-02T01:33:39Z

   - Allow to enable `suppress_(row|result)_objects` on select_by_sql and select_named.

0.86 2016-01-26T02:39:43Z

   - Split Aniki::Plugin::PagerInjector
   - Added Aniki::Plugin::SQLPager

0.85 2015-12-10T10:31:33Z

   - Fixed no disconnect if caught not a connect error at retring in Aniki::Handler::RoundRobin

Changes  view on Meta::CPAN


   - switch to old style package declation syntax for some old static code analyzers

0.08 2015-10-26T12:39:36Z

   - Split handler class from main class.
   - Added WeightedRoundRobin plugin.

0.07 2015-10-13T07:59:29Z

   - Fixed broken suppress_row_objects option of select method.

0.06 2015-10-13T00:39:45Z

   - A little refactor. (no feature changes)

0.05 2015-10-12T18:17:51Z

   - Renamed some methods/classes
   - Performance improvements
   - Support custom result class

Changes  view on Meta::CPAN


    - Keep inflated rows to keep relay cache

0.04_02 2015-09-28T08:26:21Z

    - Plugin::SelectJoined is broken when outer joined.

0.04_01 2015-09-28T07:00:10Z

    - Added Plugin::SelectJoined (inspired by Teng::Plugin::SearchJoined)
    - Support to select specified columns by table

0.03_01 2015-09-22T04:58:09Z

    - Support automatic pre-cache the inverse relational rows

0.02_09 2015-09-19T05:23:54Z

    - Support the canonicalize of the `SELECT` SQL statement
    - Support deep prefetch

MANIFEST  view on Meta::CPAN

minil.toml
script/install-aniki
t/00_compile.t
t/01_new.t
t/02_insert.t
t/03_insert_and_fetch_id.t
t/04_insert_and_fetch_row.t
t/05_insert_and_emulate_row.t
t/06_insert_multi.t
t/07_insert_on_duplicate.t
t/08_select.t
t/09_update.t
t/10_delete.t
t/11_relay.t
t/12_new_row_from_hashref.t
t/13_new_collection_from_arrayref.t
t/14_update_and_fetch_row.t
t/15_update_and_emulate_row.t
t/filter/declare/basic.t
t/filter/deflate/basic.t
t/filter/deflate/regex.t

MANIFEST  view on Meta::CPAN

t/lib/t/DB/Exception.pm
t/lib/t/DB/Filter.pm
t/lib/t/DB/Row.pm
t/lib/t/DB/Row/Author.pm
t/lib/t/DB/Schema/Common.pm
t/lib/t/DB/Schema/MySQL.pm
t/lib/t/DB/Schema/PostgreSQL.pm
t/lib/t/DB/Schema/SQLite.pm
t/lib/t/Util.pm
t/plugin/count/count.t
t/plugin/pager/select_with_pager.t
t/plugin/pager_injector/inject_pager_to_result.t
t/plugin/range_condition_maker/make_range_condition.t
t/plugin/select_joined/select_joined.t
t/plugin/sql_pager/select_by_sql_with_pager.t
t/plugin/sql_pager/select_named_with_pager.t
t/plugin/weighted_round_robin/weighted_round_robin.t
t/query_builder/canonical.t
t/row/can.t
t/row/joined/can.t
t/schema/00_has_many.t
t/schema/relationship/basic.t
t/schema/relationship/pluralize.t
t/schema/relationships/basic.t
META.yml
MANIFEST

README.md  view on Meta::CPAN


    $db->insert(module => {
        name      => 'DBIx::Schema::DSL',
        author_id => $author_id,
    });
    $db->insert(module => {
        name      => 'Riji',
        author_id => $author_id,
    });

    my $module = $db->select(module => {
        name => 'Riji',
    }, {
        limit => 1,
    })->first;
    say '$module->name:         ', $module->name;         ## Riji
    say '$module->author->name: ', $module->author->name; ## SONGMU

    my $author = $db->select(author => {
        name => 'songmu',
    }, {
        limit    => 1,
        prefetch => [qw/modules/],
    })->first;

    say '$author->name:   ', $author->name;                 ## SONGMU
    say 'modules[]->name: ', $_->name for $author->modules; ## DBIx::Schema::DSL, Riji
};

README.md  view on Meta::CPAN

        integer 'id', primary_key, auto_increment;
        varchar 'name', unique;
    };
};
```

A `author` has many `modules`.
So you can access `author` row object to `modules`.

```perl
my $author = $db->select(author => { name => 'songmu' })->first;
say 'modules[]->name: ', $_->name for $author->modules; ## DBIx::Schema::DSL, Riji
```

Also `module` has a `author`.
So you can access `module` row object to `author` also.

```perl
my $module = $db->select(module => { name => 'Riji' })->first;
say "Riji's author is ", $module->author->name; ## SONGMU
```

And you can pre-fetch related rows.

```perl
my @modules = $db->select(module => {}, { prefetch => [qw/author/] });
say $_->name, "'s author is ", $_->author->name for @modules;
```

# SETUP

Install Aniki from CPAN:

```
cpanm Aniki
```

README.md  view on Meta::CPAN

    If this option is true, no create row objects.
    Aniki's methods returns hash reference instead of row object.

- `suppress_result_objects : Bool`

    If this option is true, no create result objects.
    Aniki's methods returns array reference instead of result object.

## INSTANCE METHODS

### `select($table_name, \%where, \%opt)`

Execute `SELECT` query by generated SQL, and returns result object.

```perl
my $result = $db->select(foo => { id => 1 }, { limit => 1 });
# stmt: SELECT FROM foo WHERE id = ? LIMIT 1
# bind: [1]
```

#### Options

There are the options of `SELECT` query.
See also [SQL::Maker](https://metacpan.org/pod/SQL::Maker#opt).

And you can use there options:

README.md  view on Meta::CPAN


- `columns : ArrayRef[Str]`

    List for retrieving columns from database.

- `prefetch : ArrayRef|HashRef`

    Pre-fetch specified related rows.
    See also ["RELATIONSHIP"](#relationship) section.

### `select_named($sql, \%bind, \%opt)`

### `select_by_sql($sql, \@bind, \%opt)`

Execute `SELECT` query by specified SQL, and returns result object.

```perl
my $result = $db->select_by_sql('SELECT FROM foo WHERE id = ? LIMIT 1', [1]);
# stmt: SELECT FROM foo WHERE id = ? LIMIT 1
# bind: [1]
```

#### Options

You can use there options:

- `table_name: Str`

README.md  view on Meta::CPAN

my $count = $db->update(foo => { bar => 2 }, { id => 1 });
# stmt: UPDATE foo SET bar = ? WHERE id = ?
# bind: [2, 1]
```

### `update($row, \%set)`

Execute `UPDATE` query, and returns changed rows count.

```perl
my $row = $db->select(foo => { id => 1 }, { limit => 1 })->first;
my $count = $db->update($row => { bar => 2 });
# stmt: UPDATE foo SET bar = ? WHERE id = ?
# bind: [2, 1]
```

### `update_and_fetch_row($row, \%set)`

Execute `UPDATE` query, and `SELECT` it, and returns row object.

```perl
my $row = $db->select(foo => { id => 1 }, { limit => 1 })->first;
my $new_row = $db->update_and_fetch_row($row => { bar => 2 });
# stmt: UPDATE foo SET bar = ? WHERE id = ?
# bind: [2, 1]
```

### `update_and_emulate_row($row, \%set)`

Execute `UPDATE` query, and returns row object created by `$row` and `%set`.

```perl
my $row = $db->select(foo => { id => 1 }, { limit => 1 })->first;
my $new_row = $db->update_and_emulate_row($row => { bar => 2 });
# stmt: UPDATE foo SET bar = ? WHERE id = ?
# bind: [2, 1]
```

This method is faster than `update_and_fetch_row`.

#### WARNING

If you use SQL `TRIGGER` or `AutoCommit`, this method don't return the correct value, maybe.

README.md  view on Meta::CPAN

my $count = $db->delete(foo => { id => 1 });
# stmt: DELETE FROM foo WHERE id = ?
# bind: [1]
```

### `delete($row)`

Execute `DELETE` query, and returns changed rows count.

```perl
my $row = $db->select(foo => { id => 1 }, { limit => 1 })->first;
my $count = $db->delete($row);
# stmt: DELETE foo WHERE id = ?
# bind: [1]
```

## ACCESSORS

- `schema : Aniki::Schema`
- `filter : Aniki::Filter`
- `query_builder : Aniki::QueryBuilder`

author/benchmark.pl  view on Meta::CPAN


say '=============== SELECT ===============';
cmpthese timethese 20000 => {
    dbic => sub {
        my @rows = $dbic->resultset('Author')->search({}, { rows => 10, order_by => { -asc => 'id' } })->all;
    },
    teng => sub {
        my @rows = $teng->search('author' => {}, { limit => 10, order_by => { id => 'ASC' } })->all;
    },
    aniki => sub {
        my @rows = $aniki->select('author' => {}, { limit => 10, order_by => { id => 'ASC' } })->all;
    },
};

say '=============== UPDATE ===============';
cmpthese timethese 20000 => {
    dbic => sub {
        my $row = $dbic->resultset('Author')->single({ id => 1 });
        $row->update({ message => 'good morning' });
    },
    'teng(row)' => sub {
        my $row = $teng->single('author' => { id => 1 });
        $row->update({ message => 'good morning' });
    },
    teng => sub {
        $teng->update('author' => { message => 'good morning' }, { id => 1 });
    },
    'aniki(row)' => sub {
        my $row = $aniki->select('author' => { id => 1 }, { limit => 1 })->first;
        $aniki->update($row => { message => 'good morning' });
    },
    aniki => sub {
        $aniki->update('author' => { message => 'good morning' }, { id => 1 });
    },
};

say '=============== DELETE ===============';
my ($dbic_delete_id, $teng_delete_id, $aniki_delete_id) = (0, 0, 0);
cmpthese {

author/benchmark.pl  view on Meta::CPAN

        timethese 20000 => {
            dbic => sub {
                my $row = $dbic->resultset('Author')->single({ id => ++$dbic_delete_id });
                $row->delete;
            },
            'teng(row)' => sub {
                my $row = $teng->single('author' => { id => ++$teng_delete_id });
                $row->delete;
            },
            'aniki(row)' => sub {
                my $row = $aniki->select('author' => { id => ++$aniki_delete_id }, { limit => 1 })->first;
                $aniki->delete($row);
            },
        }
    },
    do {
        ($teng_delete_id, $aniki_delete_id) = (0, 0);
        $aniki->dbh->do('DELETE FROM author');
        $aniki->dbh->do('DELETE FROM sqlite_sequence WHERE name = ?', undef, 'author');
        $teng->dbh->do('DELETE FROM author');
        $teng->dbh->do('DELETE FROM sqlite_sequence WHERE name = ?', undef, 'author');

eg/synopsis.pl  view on Meta::CPAN


$db->insert(module => {
    name      => 'DBIx::Schema::DSL',
    author_id => $author_id,
});
$db->insert(module => {
    name      => 'Riji',
    author_id => $author_id,
});

my $module = $db->select(module => {
    name => 'Riji',
}, {
    limit => 1,
})->first;
say '$module->name:         ', $module->name;         ## Riji
say '$module->author->name: ', $module->author->name; ## SONGMU

my $author = $db->select(author => {
    name => 'songmu',
}, {
    limit    => 1,
    prefetch => [qw/modules/],
})->first;

say '$author->name:   ', $author->name;                 ## SONGMU
say 'modules[]->name: ', $_->name for $author->modules; ## DBIx::Schema::DSL, Riji

lib/Aniki.pm  view on Meta::CPAN

}

sub update_and_fetch_row {
    my ($self, $row, $set) = @_;
    croak '(Aniki#update_and_fetch_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);

    my $where = $self->_where_row_cond($row->table, $emulated_row_data);
    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(

lib/Aniki.pm  view on Meta::CPAN

    my $table_name = shift;
    my $row_data   = shift;

    my $table = $self->schema->get_table($table_name) or croak "$table_name is not defined in schema.";

    local $self->{_context} = $self->dbh;

    $self->insert($table_name, $row_data, @_);
    return unless defined wantarray;

    my $row = $self->select($table_name, $self->_where_row_cond($table, $row_data), { limit => 1, suppress_result_objects => 1 })->[0];
    return $row if $self->suppress_row_objects;

    $row->is_new(1);
    return $row;
}

sub insert_and_emulate_row {
    my ($self, $table_name, $row, $opt) = @_;

    my $table = $self->schema->get_table($table_name) or croak "$table_name is not defined in schema.";

lib/Aniki.pm  view on Meta::CPAN

                          : $pk->is_auto_increment        ? $self->last_insert_id($table->name, $pk->name)
                          : undef
                          ;
    }

    return \%where;
}

my $WILDCARD_COLUMNS = ['*'];

sub select :method {
    my ($self, $table_name, $where, $opt) = @_;
    $where //= {};
    $opt //= {};

    croak '(Aniki#select) `where` condition must be a reference.' unless ref $where;

    my $table = $self->schema->get_table($table_name);

    my $columns = exists $opt->{columns} ? $opt->{columns}
                : defined $table ? $table->field_names
                : $WILDCARD_COLUMNS;

    $where = $self->_bind_sql_type_to_args($table, $where) if defined $table;

    local $Carp::CarpLevel = $Carp::CarpLevel + 1;
    my ($sql, @bind) = $self->query_builder->select($table_name, $columns, $where, $opt);
    return $self->select_by_sql($sql, \@bind, {
        %$opt,
        table_name => $table_name,
        columns    => $columns,
    });
}

sub fetch_and_attach_relay_data {
    my ($self, $table_name, $prefetch, $rows) = @_;
    return unless @$rows;

lib/Aniki.pm  view on Meta::CPAN


sub _fetch_and_attach_relay_data {
    my ($self, $relationships, $rows, $key, $prefetch) = @_;
    my $relationship = $relationships->get($key);
    unless ($relationship) {
        croak "'$key' is not defined as relationship. (maybe possible typo?)";
    }
    $relationship->fetcher->execute($self, $rows, $prefetch);
}

sub select_named {
    my ($self, $sql, $bind, $opt) = @_;
    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';

lib/Aniki.pm  view on Meta::CPAN


        $db->insert(module => {
            name      => 'DBIx::Schema::DSL',
            author_id => $author_id,
        });
        $db->insert(module => {
            name      => 'Riji',
            author_id => $author_id,
        });

        my $module = $db->select(module => {
            name => 'Riji',
        }, {
            limit => 1,
        })->first;
        say '$module->name:         ', $module->name;         ## Riji
        say '$module->author->name: ', $module->author->name; ## SONGMU

        my $author = $db->select(author => {
            name => 'songmu',
        }, {
            limit    => 1,
            prefetch => [qw/modules/],
        })->first;

        say '$author->name:   ', $author->name;                 ## SONGMU
        say 'modules[]->name: ', $_->name for $author->modules; ## DBIx::Schema::DSL, Riji
    };

lib/Aniki.pm  view on Meta::CPAN


        create_table 'author' => columns {
            integer 'id', primary_key, auto_increment;
            varchar 'name', unique;
        };
    };

A C<author> has many C<modules>.
So you can access C<author> row object to C<modules>.

    my $author = $db->select(author => { name => 'songmu' })->first;
    say 'modules[]->name: ', $_->name for $author->modules; ## DBIx::Schema::DSL, Riji

Also C<module> has a C<author>.
So you can access C<module> row object to C<author> also.

    my $module = $db->select(module => { name => 'Riji' })->first;
    say "Riji's author is ", $module->author->name; ## SONGMU

And you can pre-fetch related rows.

    my @modules = $db->select(module => {}, { prefetch => [qw/author/] });
    say $_->name, "'s author is ", $_->author->name for @modules;

=head1 SETUP

Install Aniki from CPAN:

    cpanm Aniki

And run C<install-aniki> command.

lib/Aniki.pm  view on Meta::CPAN


=item C<suppress_result_objects : Bool>

If this option is true, no create result objects.
Aniki's methods returns array reference instead of result object.

=back

=head2 INSTANCE METHODS

=head3 C<select($table_name, \%where, \%opt)>

Execute C<SELECT> query by generated SQL, and returns result object.

    my $result = $db->select(foo => { id => 1 }, { limit => 1 });
    # stmt: SELECT FROM foo WHERE id = ? LIMIT 1
    # bind: [1]

=head4 Options

There are the options of C<SELECT> query.
See also L<SQL::Maker|https://metacpan.org/pod/SQL::Maker#opt>.

And you can use there options:

lib/Aniki.pm  view on Meta::CPAN


List for retrieving columns from database.

=item C<prefetch : ArrayRef|HashRef>

Pre-fetch specified related rows.
See also L</"RELATIONSHIP"> section.

=back

=head3 C<select_named($sql, \%bind, \%opt)>

=head3 C<select_by_sql($sql, \@bind, \%opt)>

Execute C<SELECT> query by specified SQL, and returns result object.

    my $result = $db->select_by_sql('SELECT FROM foo WHERE id = ? LIMIT 1', [1]);
    # stmt: SELECT FROM foo WHERE id = ? LIMIT 1
    # bind: [1]

=head4 Options

You can use there options:

=over 4

=item C<table_name: Str>

lib/Aniki.pm  view on Meta::CPAN

Execute C<UPDATE> query, and returns changed rows count.

    my $count = $db->update(foo => { bar => 2 }, { id => 1 });
    # stmt: UPDATE foo SET bar = ? WHERE id = ?
    # bind: [2, 1]

=head3 C<update($row, \%set)>

Execute C<UPDATE> query, and returns changed rows count.

    my $row = $db->select(foo => { id => 1 }, { limit => 1 })->first;
    my $count = $db->update($row => { bar => 2 });
    # stmt: UPDATE foo SET bar = ? WHERE id = ?
    # bind: [2, 1]

=head3 C<update_and_fetch_row($row, \%set)>

Execute C<UPDATE> query, and C<SELECT> it, and returns row object.

    my $row = $db->select(foo => { id => 1 }, { limit => 1 })->first;
    my $new_row = $db->update_and_fetch_row($row => { bar => 2 });
    # stmt: UPDATE foo SET bar = ? WHERE id = ?
    # bind: [2, 1]

=head3 C<update_and_emulate_row($row, \%set)>

Execute C<UPDATE> query, and returns row object created by C<$row> and C<%set>.

    my $row = $db->select(foo => { id => 1 }, { limit => 1 })->first;
    my $new_row = $db->update_and_emulate_row($row => { bar => 2 });
    # stmt: UPDATE foo SET bar = ? WHERE id = ?
    # bind: [2, 1]

This method is faster than C<update_and_fetch_row>.

=head4 WARNING

If you use SQL C<TRIGGER> or C<AutoCommit>, this method don't return the correct value, maybe.
In this case, you should use C<update_and_fetch_row> instead of this method.

lib/Aniki.pm  view on Meta::CPAN

Execute C<DELETE> query, and returns changed rows count.

    my $count = $db->delete(foo => { id => 1 });
    # stmt: DELETE FROM foo WHERE id = ?
    # bind: [1]

=head3 C<delete($row)>

Execute C<DELETE> query, and returns changed rows count.

    my $row = $db->select(foo => { id => 1 }, { limit => 1 })->first;
    my $count = $db->delete($row);
    # stmt: DELETE foo WHERE id = ?
    # bind: [1]

=head2 ACCESSORS

=over 4

=item C<schema : Aniki::Schema>

lib/Aniki/Plugin/Count.pm  view on Meta::CPAN

    my ($self, $table, $column, $where, $opt) = @_;
    $where //= {};
    $column //= '*';

    croak '(Aniki::Plugin::Count#count) `where` condition must be a reference.' unless ref $where;

    if (ref $column) {
        croak 'Do not pass HashRef/ArrayRef to second argument. Usage: $db->count($table[, $column[, $where[, $opt]]])';
    }

    my ($sql, @binds) = $self->query_builder->select($table, [\"COUNT($column)"], $where, $opt);
    my ($count) = $self->dbh->selectrow_array($sql, undef, @binds);
    return $count;
}

1;
__END__

=pod

=encoding utf-8

lib/Aniki/Plugin/Pager.pm  view on Meta::CPAN

package Aniki::Plugin::Pager;
use 5.014002;

use namespace::autoclean;
use Mouse::Role;

use Carp qw/croak/;

requires qw/select/;
with qw/Aniki::Plugin::PagerInjector/;
with qw/Aniki::Plugin::RangeConditionMaker/;

sub select_with_pager {
    my ($self, $table_name, $where, $opt) = @_;
    $where //= {};
    $opt //= {};

    croak '(Aniki::Plugin::Pager#select_with_pager) `where` condition must be a reference.' unless ref $where;

    my $range_condition = $self->make_range_condition($opt);
    if ($range_condition) {
        ref $where eq 'HASH'
            or croak "where condition *MUST* be HashRef when using range codition.";

        for my $column (keys %$range_condition) {
            croak "Conflict range condition and where condition for $table_name.$column"
                if exists $where->{$column};
        }

        $where = {%$where, %$range_condition};
    }

    my $page = $opt->{page} or Carp::croak("required parameter: page");
    my $rows = $opt->{rows} or Carp::croak("required parameter: rows");
    my $result = $self->select($table_name => $where, {
        %$opt,
        limit  => $rows + 1,
        !$range_condition ? (
            offset => $rows * ($page - 1),
        ) : (),
    });

    return $self->inject_pager_to_result($result => {
        rows => $rows,
        page => $page,

lib/Aniki/Plugin/Pager.pm  view on Meta::CPAN


=head1 SYNOPSIS

    package MyDB;
    use Mouse v2.4.5;
    extends qw/Aniki/;
    with qw/Aniki::Plugin::Pager/;

    package main;
    my $db = MyDB->new(...);
    my $result = $db->select_with_pager('user', { type => 2 }, { page => 1, rows => 10 }); # => Aniki::Result::Collection(+Aniki::Result::Role::Pager)
    $result->pager; # => Data::Page::NoTotalEntries

=head1 SEE ALSO

L<perl>

=head1 LICENSE

Copyright (C) karupanerura.

lib/Aniki/Plugin/PagerInjector.pm  view on Meta::CPAN

    package MyDB;
    use Mouse v2.4.5;
    extends qw/Aniki/;
    with qw/Aniki::Plugin::PagerInjector/;

    package main;
    my $db = MyDB->new(...);

    my ($page, $rows) = (1, 10);
    my ($limit, $offset) = ($rows + 1, ($page - 1) * $rows);
    my $result = $db->select('user', { type => 2 }, { limit => $limit, offset => $offset }); # => Aniki::Result::Collection
    $result = $db->inject_pager_to_result($result => { # => inject Aniki::Result::Role::Pager
        table_name => 'user',
        rows       => $rows,
        page       => $page,
    })
    $result->pager; # => Data::Page::NoTotalEntries

=head1 SEE ALSO

L<perl>

lib/Aniki/Plugin/SQLPager.pm  view on Meta::CPAN

package Aniki::Plugin::SQLPager;
use 5.014002;

use namespace::autoclean;
use Mouse::Role;

requires qw/select_by_sql select_named/;
with qw/Aniki::Plugin::PagerInjector/;

sub select_by_sql_with_pager {
    my ($self, $sql, $bind, $opt) = @_;
    $opt //= {};

    my $page = $opt->{page} or Carp::croak("required parameter: page");
    my $rows = $opt->{rows} or Carp::croak("required parameter: rows");

    my $limit  = $rows + 1;
    my $offset = $rows * ($page - 1);
    if ($opt->{no_offset}) {
        $sql .= sprintf ' LIMIT %d', $limit;
    }
    else {
        $sql .= sprintf ' LIMIT %d OFFSET %d', $limit, $offset;
    }

    my $result = $self->select_by_sql($sql, $bind, $opt);
    return $self->inject_pager_to_result($result => $opt);
}

sub select_named_with_pager {
    my ($self, $sql, $bind, $opt) = @_;
    $opt //= {};

    my $page = $opt->{page} or Carp::croak("required parameter: page");
    my $rows = $opt->{rows} or Carp::croak("required parameter: rows");

    my $limit  = $rows + 1;
    my $offset = $rows * ($page - 1);
    if ($opt->{no_offset}) {
        $sql .= sprintf ' LIMIT %d', $limit;
    }
    else {
        $sql .= sprintf ' LIMIT %d OFFSET %d', $limit, $offset;
    }

    my $result = $self->select_named($sql, $bind, $opt);
    return $self->inject_pager_to_result($result => $opt);
}

1;
__END__

=pod

=for stopwords sql

lib/Aniki/Plugin/SQLPager.pm  view on Meta::CPAN


=head1 SYNOPSIS

    package MyDB;
    use Mouse v2.4.5;
    extends qw/Aniki/;
    with qw/Aniki::Plugin::Pager/;

    package main;
    my $db = MyDB->new(...);
    my $result = $db->select_by_sql_with_pager('SELECT * FROM user WHERE type = ?', [ 2 ], { page => 1, rows => 10 }); # => Aniki::Result::Collection(+Aniki::Result::Role::Pager)
    # ALSO OK: my $result = $db->select_named_with_pager('SELECT * FROM user WHERE type = :type', { type => 2 }, { page => 1, rows => 10 }); # => Aniki::Result::Collection(+Aniki::Result::Role::Pager)
    $result->pager; # => Data::Page::NoTotalEntries

=head1 SEE ALSO

L<perl>

=head1 LICENSE

Copyright (C) karupanerura.

lib/Aniki/Plugin/SelectJoined.pm  view on Meta::CPAN

use namespace::autoclean;
use Mouse::Role;
use Aniki::QueryBuilder;
use Aniki::Result::Collection::Joined;
use Carp qw/croak/;

requires qw/schema query_builder suppress_row_objects txn_manager execute/;

Aniki::QueryBuilder->load_plugin('JoinSelect');

sub select_joined {
    my ($self, $base_table, $join_conditions, $where, $opt) = @_;
    croak '(Aniki::Plugin::SelectJoined#select_joined) `where` condition must be a reference.' unless ref $where;

    my @table_names = ($base_table);
    for (my $i = 0; my $table = $join_conditions->[$i]; $i += 2) {
        push @table_names => $table;
    }
    my @tables = map { $self->schema->get_table($_) } @table_names;

    my $name_sep = $self->query_builder->name_sep;
    my @columns;
    for my $table (@tables) {
        my $table_name = $table->name;
        push @columns =>
            map { "$table_name$name_sep$_" }
            map { $_->name } $table->get_fields();
    }

    my ($sql, @bind) = $self->query_builder->join_select($base_table, $join_conditions, \@columns, $where, $opt);
    return $self->select_joined_by_sql($sql, \@bind, {
        table_names => \@table_names,
        columns     => \@columns,
        %$opt,
    });
}

sub select_joined_by_sql {
    my ($self, $sql, $bind, $opt) = @_;
    $opt //= {};

    my $table_names = $opt->{table_names} or croak 'table_names is required';
    my $columns     = $opt->{columns}     or croak 'columns is required';
    my $prefetch    = exists $opt->{prefetch} ? $opt->{prefetch} : {};

    my $prefetch_enabled_fg = %$prefetch && !$self->suppress_row_objects;
    if ($prefetch_enabled_fg) {
        my $txn; $txn = $self->txn_scope unless $self->txn_manager->in_transaction;

lib/Aniki/Plugin/SelectJoined.pm  view on Meta::CPAN

=head1 SYNOPSIS

    package MyDB;
    use Mouse v2.4.5;
    extends qw/Aniki/;
    with qw/Aniki::Plugin::SelectJoined/;

    package main;
    my $db = MyDB->new(...);

    my $result = $db->select_joined(user_item => [
        user => {'user_item.user_id' => 'user.id'},
        item => {'user_item.item_id' => 'item.id'},
    ], {
        'user.id' => 2,
    }, {
        order_by => 'user_item.item_id',
    });

    for my $row ($result->all) {
        my $user_item = $row->user_item;

lib/Aniki/QueryBuilder/Canonical.pm  view on Meta::CPAN

sub delete :method {
    my ($self, $table, $where, $opt) = @_;
    if (ref $where eq 'HASH') {
        $where = [
            map { $_ => $where->{$_} } sort keys %$where
        ];
    }
    return $self->SUPER::delete($table, $where, $opt);
}

sub select_query {
    my ($self, $table, $fields, $where, $opt) = @_;
    if (ref $where eq 'HASH') {
        $where = [
            map { $_ => $where->{$_} } sort keys %$where
        ];
    }
    return $self->SUPER::select_query($table, $fields, $where, $opt);
}

1;
__END__

lib/Aniki/Result.pm  view on Meta::CPAN

=pod

=encoding utf-8

=head1 NAME

Aniki::Result - Result class

=head1 SYNOPSIS

    my $result = $db->select(foo => { bar => 1 });

=head1 DESCRIPTION

This is abstract result class.

Aniki detect the collection class from root result class by table name.
Default root result class is C<MyApp::DB::Collection>.

You can use original result class:

lib/Aniki/Result/Collection.pm  view on Meta::CPAN

=pod

=encoding utf-8

=head1 NAME

Aniki::Result::Collection - Rows as a collection

=head1 SYNOPSIS

    my $result = $db->select(foo => { bar => 1 });
    for my $row ($result->all) {
        print $row->id, "\n";
    }

=head1 DESCRIPTION

This is collection result class.

=head1 INSTANCE METHODS

lib/Aniki/Row.pm  view on Meta::CPAN

    }
    return \%row;
}

sub refetch {
    my ($self, $opts) = @_;
    $opts //= +{};
    $opts->{limit} = 1;

    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) = @_;

lib/Aniki/Row.pm  view on Meta::CPAN

=pod

=encoding utf-8

=head1 NAME

Aniki::Row - Row class

=head1 SYNOPSIS

    my $result = $db->select(foo => { bar => 1 });
    for my $row ($result->all) {
        print $row->id, "\n";
    }

=head1 DESCRIPTION

This is row class.

=head1 INSTANCE METHODS

lib/Aniki/Schema/Relationship/Fetcher.pm  view on Meta::CPAN


        my @src_values = grep defined, map { $_->get_column($src_column) } @$rows;
        unless (@src_values) {
            # set empty value
            for my $row (@$rows) {
                $row->relay_data->{$name} = $has_many ? [] : undef;
            }
            return;
        }

        my @related_rows = $handler->select($table_name => {
            %where,
            $dest_column => sql_in(\@src_values),
        }, { prefetch => $prefetch })->all;

        my %related_rows_map = partition_by { $_->get_column($dest_column) } @related_rows;
        for my $row (@$rows) {
            my $src_value = $row->get_column($src_column);
            unless (defined $src_value) {
                # set empty value
                $row->relay_data->{$name} = $has_many ? [] : undef;

lib/Aniki/Schema/Relationship/Fetcher.pm  view on Meta::CPAN

            my $related_rows = $related_rows_map{$src_value};
            $row->relay_data->{$name} = $has_many ? $related_rows : $related_rows->[0];
        }

        $self->_execute_inverse(\@related_rows => $rows);
    }
    else {
        # follow slow case...
        for my $row (@$rows) {
            next if notall { defined $row->get_column($_) } @src_columns;
            my @related_rows = $handler->select($table_name => {
                %where,
                pairwise { $a => $row->get_column($b) } @dest_columns, @src_columns
            }, { prefetch => $prefetch })->all;
            $row->relay_data->{$name} = $has_many ? \@related_rows : $related_rows[0];
        }
    }
}

sub _execute_inverse {
    my ($self, $src_rows, $dest_rows) = @_;

t/02_insert.t  view on Meta::CPAN

use utf8;

use Test::More;

use File::Spec;
use lib File::Spec->catfile('t', 'lib');
use t::Util;

run_on_database {
    db->insert(author => { name => 'MOZNION' });
    is db->select(author => {}, { limit => 1 })->count, 1, 'created.';
};

done_testing();

t/03_insert_and_fetch_id.t  view on Meta::CPAN

use Test::More;

use File::Spec;
use lib File::Spec->catfile('t', 'lib');
use t::Util;

run_on_database {
    my $id = db->insert_and_fetch_id(author => { name => 'MOZNION' });
    ok defined $id, 'id is defined.';

    my $row = db->select(author => { id => $id }, { limit => 1 })->first;
    is_deeply $row->get_columns, {
        id              => $id,
        name            => 'MOZNION',
        message         => 'hello',
        inflate_message => 'hello',
        deflate_message => 'hello',
    }, 'Data is valid.';
};

done_testing();

t/06_insert_multi.t  view on Meta::CPAN


run_on_database {
    is query_count {
        db->insert_multi(author => []);
    }, 0, 'nothing to do if empty values';

    db->insert_multi(author => [
        { name => 'MOZNION',  message => 'hoge' },
        { name => 'PAPIX',    message => 'fuga' },
    ]);
    is db->select(author => {}, {})->count, 2, 'created.';
    is db->select(author => { name => 'PAPIX' }, { limit => 1 })->first->message, 'fuga';

    if (db->query_builder->driver eq 'mysql') {
        db->insert_multi(author => [
            { name => 'PAPIX',  message => 'hoge' },
            { name => 'KARUPA', message => 'fuga' },
        ], {
            update => {
                message => sql_raw('VALUES(message)'),
            }
        });
        is db->select(author => {}, {})->count, 3, 'created.';
        is db->select(author => { name => 'PAPIX' }, { limit => 1 })->first->message, 'hoge';
    };
};

done_testing();

t/07_insert_on_duplicate.t  view on Meta::CPAN


use SQL::QueryMaker qw/sql_raw/;

run_on_each_databases [qw/MySQL/] => sub {
    db->insert_on_duplicate(author => {
        name => 'PAPIX',
        message => 'hoge',
    }, {
        message => sql_raw('VALUES(message)'),
    });
    is db->select(author => {}, {})->count, 1, 'created.';
    is db->select(author => { name => 'PAPIX' }, { limit => 1 })->first->message, 'hoge';

    db->insert_on_duplicate(author => {
        name => 'PAPIX',
        message => 'fuga',
    }, {
        message => sql_raw('VALUES(message)'),
    });
    is db->select(author => {}, {})->count, 1, 'updated.';
    is db->select(author => { name => 'PAPIX' }, { limit => 1 })->first->message, 'fuga';
};

done_testing();

t/08_select.t  view on Meta::CPAN


use Test::More;

use File::Spec;
use lib File::Spec->catfile('t', 'lib');
use t::Util;

run_on_database {
    db->insert(author => { name => 'MOZNION' });

    my $rows = db->select(author => {});
    isa_ok $rows, 'Aniki::Result::Collection';
    is $rows->count, 1;
    isa_ok $rows->first, 'Aniki::Row';

    $rows = db->select(author => {
        name => 'OBAKE'
    });
    isa_ok $rows, 'Aniki::Result::Collection';
    is $rows->count, 0;

    $rows = db->select(author => {}, { suppress_row_objects => 1 });
    isa_ok $rows, 'Aniki::Result::Collection';
    is $rows->count, 1;
    isa_ok $rows->first, 'HASH';

    $rows = db->select(author => {}, { suppress_result_objects => 1 });
    isa_ok $rows, 'ARRAY';
    is @$rows, 1;
    isa_ok $rows->[0], 'Aniki::Row';

    $rows = db->select(author => {}, { suppress_result_objects => 1, suppress_row_objects => 1 });
    isa_ok $rows, 'ARRAY';
    is @$rows, 1;
    isa_ok $rows->[0], 'HASH';
};

done_testing();

t/09_update.t  view on Meta::CPAN

use Test::More;

use File::Spec;
use lib File::Spec->catfile('t', 'lib');
use t::Util;

run_on_database {
    db->insert(author => { name => 'MOZNION' });
    db->update(author => { name => 'MOZNION2' }, { name => 'MOZNION' });

    my $rows = db->select(author => {});
    isa_ok $rows, 'Aniki::Result::Collection';
    is $rows->count, 1;
    is $rows->first->name, 'MOZNION2', 'updated';

    my $row = $rows->first;
    my $cnt = db->update($row => { name => 'MOZNION' });
    is $row->name, 'MOZNION2', 'old value';
    is $cnt, 1, 'a row is changed';

    my $new_row = $row->refetch;

t/10_delete.t  view on Meta::CPAN

use Test::More;

use File::Spec;
use lib File::Spec->catfile('t', 'lib');
use t::Util;

run_on_database {
    db->insert(author => { name => 'MOZNION' });
    db->insert(author => { name => 'MOZNION2' });

    is db->select(author => {})->count, 2;

    db->delete(author => { name => 'MOZNION' });

    my $rows = db->select(author => {});
    is $rows->count, 1;

    my $row = $rows->first;
    is $row->name, 'MOZNION2';

    db->delete($row);

    is db->select(author => {})->count, 0;

    my ($line, $file);
    eval { db->delete(author => 'id = 1') }; ($line, $file) = (__LINE__, __FILE__);
    like $@, qr/^\Q(Aniki#delete) `where` condition must be a reference at $file line $line/, 'croak with no set parameters';
};

done_testing();

t/11_relay.t  view on Meta::CPAN


    my $mamimu_id = db->insert_and_fetch_id(author => { name => 'MAMIMU' });

    db->insert_multi(version => [map {
        +{ name => '0.01', module_id => $_ },
    } @moznion_module_ids, @karupa_module_ids]);

    subtest 'shallow' => sub {
        subtest 'prefetch' => sub {
            my $queries = query_count {
                my $rows = db->select(author => {}, { prefetch => [qw/modules/] });
                isa_ok $rows, 'Aniki::Result::Collection';
                is $rows->count, 3;
                ok $_->is_prefetched('modules') for $rows->all;

                my %modules = map { $_->name => [sort map { $_->name } $_->modules] } $rows->all;
                is_deeply \%modules, {
                    MOZNION => [qw/Perl::Lint Regexp::Lexer Test::JsonAPI::Autodoc/],
                    KARUPA  => [qw/Plack::App::Vhost TOML::Parser Test::SharedObject/],
                    MAMIMU  => [],
                };
            };
            is $queries, 2;
        };

        subtest 'lazy' => sub {
            my $queries = query_count {
                my $rows = db->select(author => {});
                isa_ok $rows, 'Aniki::Result::Collection';
                is $rows->count, 3;
                ok !$_->is_prefetched('modules') for $rows->all;

                my %modules = map { $_->name => [sort map { $_->name } $_->modules] } $rows->all;
                is_deeply \%modules, {
                    MOZNION => [qw/Perl::Lint Regexp::Lexer Test::JsonAPI::Autodoc/],
                    KARUPA  => [qw/Plack::App::Vhost TOML::Parser Test::SharedObject/],
                    MAMIMU  => [],
                };
            };
            is $queries, 4;
        };
    };

    subtest 'deep' => sub {
        subtest 'prefetch' => sub {
            my $queries = query_count {
                my $rows = db->select(author => {}, { prefetch => { modules => [qw/versions/] } });
                isa_ok $rows, 'Aniki::Result::Collection';
                is $rows->count, 3;
                for my $row ($rows->all) {
                    ok $row->is_prefetched('modules');
                    ok $_->is_prefetched('versions') for $row->modules;
                }

                my %modules = map {
                    $_->name => +{
                        map {

t/11_relay.t  view on Meta::CPAN

                    },
                    MAMIMU  => +{
                    },
                };
            };
            is $queries, 3;
        };

        subtest 'lazy' => sub {
            my $queries = query_count {
                my $rows = db->select(author => {});
                isa_ok $rows, 'Aniki::Result::Collection';
                is $rows->count, 3;
                for my $row ($rows->all) {
                    ok !$row->is_prefetched('modules');
                    ok !$_->is_prefetched('versions') for $row->modules;
                }

                my %modules = map {
                    $_->name => +{
                        map {

t/11_relay.t  view on Meta::CPAN

                    },
                };
            };
            is $queries, 10;
        };
    };

    subtest 'inverse' => sub {
        subtest 'prefetch' => sub {
            my $queries = query_count {
                my $rows = db->select(author => {}, { prefetch => { modules => [qw/versions/] } });
                isa_ok $rows, 'Aniki::Result::Collection';
                is $rows->count, 3;
                for my $row ($rows->all) {
                    ok $row->is_prefetched('modules');
                    ok $_->is_prefetched('versions') for $row->modules;
                }

                my %modules = map { $_->versions->[0]->module->name => [$_->author->name, map { $_->name } @{ $_->versions }] } map { $_->modules } $rows->all;
                is_deeply \%modules, {
                    'Perl::Lint'             => ['MOZNION', '0.01'],

t/11_relay.t  view on Meta::CPAN

                    'Plack::App::Vhost'      => ['KARUPA',  '0.01'],
                    'TOML::Parser'           => ['KARUPA',  '0.01'],
                    'Test::SharedObject'     => ['KARUPA',  '0.01'],
                } or diag explain \%modules;
            };
            is $queries, 3;
        };

        subtest 'lazy' => sub {
            my $queries = query_count {
                my $rows = db->select(author => {});
                isa_ok $rows, 'Aniki::Result::Collection';
                is $rows->count, 3;
                for my $row ($rows->all) {
                    ok !$row->is_prefetched('modules');
                    ok !$_->is_prefetched('versions') for $row->modules;
                }

                my %modules = map { $_->versions->[0]->module->name => [$_->author->name, map { $_->name } @{ $_->versions }] } map { $_->modules } $rows->all;
                is_deeply \%modules, {
                    'Perl::Lint'             => ['MOZNION', '0.01'],

t/11_relay.t  view on Meta::CPAN

            is $queries, 10;
        };
    };

    db->insert_multi(version => [map {
        +{ name => '0.02', module_id => $_ },
    } @moznion_module_ids, @karupa_module_ids]);

    subtest 'extra where conditions' => sub {
        my $queries = query_count {
            my $rows = db->select(author => {}, { prefetch => { modules => { versions => { '.name' => '0.02' } } } });
            isa_ok $rows, 'Aniki::Result::Collection';
            is $rows->count, 3;
            for my $row ($rows->all) {
                ok $row->is_prefetched('modules');
                ok $_->is_prefetched('versions') for $row->modules;
            }

            my %modules = map {
                $_->name => +{
                    map {

t/plugin/pager/select_with_pager.t  view on Meta::CPAN

use t::Util;

run_on_database {
    Mouse::Util::apply_all_roles(db, 'Aniki::Plugin::Pager');

    db->insert_multi(author => [map {
        +{ name => $_ }
    } qw/MOZNION KARUPA PAPIX/]);

    subtest 'ASC' => sub {
        my $rows = db->select_with_pager(author => {}, { rows => 2, page => 1, order_by => { id => 'ASC' } });
        isa_ok $rows, 'Aniki::Result::Collection';
        ok $rows->meta->does_role('Aniki::Result::Role::Pager');
        is $rows->count, 2;

        isa_ok $rows->pager, 'Data::Page::NoTotalEntries';
        is $rows->pager->current_page, 1;
        ok $rows->pager->has_next;

        $rows = db->select_with_pager(author => {}, { rows => 2, page => 2, order_by => { id => 'ASC' } });
        isa_ok $rows, 'Aniki::Result::Collection';
        ok $rows->meta->does_role('Aniki::Result::Role::Pager');
        is $rows->count, 1;
        is $rows->first->id, 3;

        isa_ok $rows->pager, 'Data::Page::NoTotalEntries';
        is $rows->pager->current_page, 2;
        ok !$rows->pager->has_next;

        $rows = db->select_with_pager(author => {}, { rows => 2, page => 2, order_by => { id => 'ASC' }, lower => { id => 2 } });
        isa_ok $rows, 'Aniki::Result::Collection';
        ok $rows->meta->does_role('Aniki::Result::Role::Pager');
        is $rows->count, 1;
        is $rows->first->id, 3;

        isa_ok $rows->pager, 'Data::Page::NoTotalEntries';
        is $rows->pager->current_page, 2;
        ok !$rows->pager->has_next;
    };

    subtest 'DESC' => sub {
        my $rows = db->select_with_pager(author => {}, { rows => 2, page => 1, order_by => { id => 'DESC' } });
        isa_ok $rows, 'Aniki::Result::Collection';
        ok $rows->meta->does_role('Aniki::Result::Role::Pager');
        is $rows->count, 2;
        is $rows->first->id, 3;

        isa_ok $rows->pager, 'Data::Page::NoTotalEntries';
        is $rows->pager->current_page, 1;
        ok $rows->pager->has_next;

        $rows = db->select_with_pager(author => {}, { rows => 2, page => 2, order_by => { id => 'DESC' } });
        isa_ok $rows, 'Aniki::Result::Collection';
        ok $rows->meta->does_role('Aniki::Result::Role::Pager');
        is $rows->count, 1;
        is $rows->first->id, 1;

        isa_ok $rows->pager, 'Data::Page::NoTotalEntries';
        is $rows->pager->current_page, 2;
        ok !$rows->pager->has_next;

        $rows = db->select_with_pager(author => {}, { rows => 2, page => 2, order_by => { id => 'DESC' }, upper => { id => 2 } });
        isa_ok $rows, 'Aniki::Result::Collection';
        ok $rows->meta->does_role('Aniki::Result::Role::Pager');
        is $rows->count, 1;
        is $rows->first->id, 1;

        isa_ok $rows->pager, 'Data::Page::NoTotalEntries';
        is $rows->pager->current_page, 2;
        ok !$rows->pager->has_next;
    };
};

t/plugin/pager_injector/inject_pager_to_result.t  view on Meta::CPAN

use Aniki::Plugin::PagerInjector;
use t::Util;

run_on_database {
    Mouse::Util::apply_all_roles(db, 'Aniki::Plugin::PagerInjector');

    db->insert_multi(author => [map {
        +{ name => $_ }
    } qw/MOZNION KARUPA PAPIX/]);

    my $rows = db->select(author => {}, { limit => 3, offset => 0 });
    isa_ok $rows, 'Aniki::Result::Collection';
    ok !$rows->meta->does_role('Aniki::Result::Role::Pager');
    is $rows->count, 3;

    $rows = db->inject_pager_to_result($rows => { page => 1, rows => 2 });
    isa_ok $rows, 'Aniki::Result::Collection';
    ok $rows->meta->does_role('Aniki::Result::Role::Pager');
    is $rows->count, 2;
    isa_ok $rows->pager, 'Data::Page::NoTotalEntries';
    is $rows->pager->current_page, 1;
    ok $rows->pager->has_next;

    $rows = db->select(author => {}, { limit => 3, offset => 2 });
    isa_ok $rows, 'Aniki::Result::Collection';
    ok !$rows->meta->does_role('Aniki::Result::Role::Pager');
    is $rows->count, 1;

    $rows = db->inject_pager_to_result($rows => { page => 2, rows => 2 });
    isa_ok $rows, 'Aniki::Result::Collection';
    ok $rows->meta->does_role('Aniki::Result::Role::Pager');
    is $rows->count, 1;
    isa_ok $rows->pager, 'Data::Page::NoTotalEntries';
    is $rows->pager->current_page, 2;

t/plugin/range_condition_maker/make_range_condition.t  view on Meta::CPAN

    Mouse::Util::apply_all_roles(db, 'Aniki::Plugin::RangeConditionMaker');

    db->insert_multi(author => [map {
        +{ name => $_ }
    } qw/MOZNION KARUPA PAPIX MACKEE/]);

    my ($where, $result);

    for my $type (qw/lower gt/) {
        $where  = db->make_range_condition({ $type => { id => 2 } });
        $result = db->select('author', $where);
        is scalar (map { $_->{id} > 2 } @{ $result->row_datas }), 2;
    }

    for my $type (qw/upper lt/) {
        $where  = db->make_range_condition({ $type => { id => 4 } });
        $result = db->select('author', $where);
        is scalar (map { $_->{id} < 4 } @{ $result->row_datas }), 3;
    }

    $where  = db->make_range_condition({ ge => { id => 2 } });
    $result = db->select('author', $where);
    is scalar (map { $_->{id} >= 2 } @{ $result->row_datas }), 3;

    $where  = db->make_range_condition({ le => { id => 4 } });
    $result = db->select('author', $where);
    is scalar (map { $_->{id} <= 4 } @{ $result->row_datas }), 4;

    $where  = db->make_range_condition({ lower => { id => 1 }, upper => { id => 3 } });
    $result = db->select('author', $where);
    is scalar @{$result->row_datas}, 1;
    is $result->row_datas->[0]->{id}, 2;
};

done_testing();

t/plugin/select_joined/select_joined.t  view on Meta::CPAN

        db->insert_and_fetch_id(module => { name => 'TOML::Parser',        author_id => $karupa_id }),
        db->insert_and_fetch_id(module => { name => 'Plack::App::Vhost',   author_id => $karupa_id }),
        db->insert_and_fetch_id(module => { name => 'Test::SharedObject',  author_id => $karupa_id }),
    );

    my $obake1_id = db->insert_and_fetch_id(author => { name => 'OBAKE1' });
    my $obake2_id = db->insert_and_fetch_id(author => { name => 'OBAKE2' });
    my $obake3_id = db->insert_and_fetch_id(author => { name => 'OBAKE3' });

    subtest normal => sub {
        my $result = db->select_joined(author => [
            module => { 'module.author_id' => 'author.id' },
        ], {
            'author.id' => $moznion_id,
        }, {
            order_by => 'module.id',
        });

        my @authors = $result->all('author');
        my @modules = $result->all('module');
        is scalar @authors, 1;

t/plugin/select_joined/select_joined.t  view on Meta::CPAN


                is query_count { $module->versions }, 1;

                my $expected = shift @expected;
                is $module->name, $expected;
            }
        };
    };

    subtest outer => sub {
        my $result = db->select_joined(author => [
            module => [LEFT => { 'module.author_id' => 'author.id' }],
        ], {
            # anywhere
        }, {
            order_by => ['author.id', 'module.id'],
        });

        my @authors = $result->all('author');
        my @modules = $result->all('module');
        is scalar @authors, 5;

t/plugin/select_joined/select_joined.t  view on Meta::CPAN

                is $module->table_name, 'module';

                my $expected = shift @expected;
                is $author->name, $expected->{author};
                is $module->name, $expected->{module};
            }
        };
    };

    subtest prefetch => sub {
        my $result = db->select_joined(author => [
            module => { 'module.author_id' => 'author.id' },
        ], {
            'author.id' => $moznion_id,
        }, {
            order_by => 'module.id',
            prefetch => {
                module => [qw/versions/],
            }
        });

t/plugin/sql_pager/select_by_sql_with_pager.t  view on Meta::CPAN

use Aniki::Plugin::SQLPager;
use t::Util;

run_on_database {
    Mouse::Util::apply_all_roles(db, 'Aniki::Plugin::SQLPager');

    db->insert_multi(author => [map {
        +{ name => $_ }
    } qw/MOZNION KARUPA PAPIX/]);

    my $rows = db->select_by_sql_with_pager('SELECT * FROM author ORDER BY id', [], { rows => 2, page => 1 });
    isa_ok $rows, 'Aniki::Result::Collection';
    ok $rows->meta->does_role('Aniki::Result::Role::Pager');
    is $rows->count, 2;
    is $rows->first->id, 1;

    isa_ok $rows->pager, 'Data::Page::NoTotalEntries';
    is $rows->pager->current_page, 1;
    ok $rows->pager->has_next;

    $rows = db->select_by_sql_with_pager('SELECT * FROM author ORDER BY id', [], { rows => 2, page => 2 });
    isa_ok $rows, 'Aniki::Result::Collection';
    ok $rows->meta->does_role('Aniki::Result::Role::Pager');
    is $rows->count, 1;
    is $rows->first->id, 3;

    isa_ok $rows->pager, 'Data::Page::NoTotalEntries';
    is $rows->pager->current_page, 2;
    ok !$rows->pager->has_next;

    $rows = db->select_by_sql_with_pager('SELECT * FROM author WHERE id > ? ORDER BY id', [2], { rows => 2, page => 2, no_offset => 1 });
    isa_ok $rows, 'Aniki::Result::Collection';
    ok $rows->meta->does_role('Aniki::Result::Role::Pager');
    is $rows->count, 1;
    is $rows->first->id, 3;

    isa_ok $rows->pager, 'Data::Page::NoTotalEntries';
    is $rows->pager->current_page, 2;
    ok !$rows->pager->has_next;
};

t/plugin/sql_pager/select_named_with_pager.t  view on Meta::CPAN

use Aniki::Plugin::SQLPager;
use t::Util;

run_on_database {
    Mouse::Util::apply_all_roles(db, 'Aniki::Plugin::SQLPager');

    db->insert_multi(author => [map {
        +{ name => $_ }
    } qw/MOZNION KARUPA PAPIX/]);

    my $rows = db->select_named_with_pager('SELECT * FROM author ORDER BY id', {}, { rows => 2, page => 1 });
    isa_ok $rows, 'Aniki::Result::Collection';
    ok $rows->meta->does_role('Aniki::Result::Role::Pager');
    is $rows->count, 2;

    isa_ok $rows->pager, 'Data::Page::NoTotalEntries';
    is $rows->pager->current_page, 1;
    ok $rows->pager->has_next;

    $rows = db->select_named_with_pager('SELECT * FROM author ORDER BY id', {}, { rows => 2, page => 2 });
    isa_ok $rows, 'Aniki::Result::Collection';
    ok $rows->meta->does_role('Aniki::Result::Role::Pager');
    is $rows->count, 1;

    isa_ok $rows->pager, 'Data::Page::NoTotalEntries';
    is $rows->pager->current_page, 2;
    ok !$rows->pager->has_next;

    $rows = db->select_named_with_pager('SELECT * FROM author WHERE id > :id ORDER BY id', { id => 2 }, { rows => 2, page => 2, no_offset => 1 });
    isa_ok $rows, 'Aniki::Result::Collection';
    ok $rows->meta->does_role('Aniki::Result::Role::Pager');
    is $rows->count, 1;
    is $rows->first->id, 3;

    isa_ok $rows->pager, 'Data::Page::NoTotalEntries';
    is $rows->pager->current_page, 2;
    ok !$rows->pager->has_next;
};



( run in 1.445 second using v1.01-cache-2.11-cpan-49f99fa48dc )