/Aniki

The ORM as our great brother.

Primary LanguagePerlOtherNOASSERTION

Build Status Coverage Status MetaCPAN Release

NAME

Aniki - The ORM as our great brother.

SYNOPSIS

use 5.014002;
package MyProj::DB::Schema {
    use DBIx::Schema::DSL;

    create_table 'module' => columns {
        integer 'id', primary_key, auto_increment;
        varchar 'name';
        integer 'author_id';

        add_index 'author_id_idx' => ['author_id'];

        belongs_to 'author';
    };

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

package MyProj::DB::Filter {
    use Aniki::Filter::Declare;
    use Scalar::Util qw/blessed/;
    use Time::Moment;

    # define inflate/deflate filters in table context.
    table author => sub {
        inflate name => sub {
            my $name = shift;
            return uc $name;
        };

        deflate name => sub {
            my $name = shift;
            return lc $name;
        };
    };

    inflate qr/_at$/ => sub {
        my $datetime = shift;
        $datetime =~ tr/ /T/;
        $datetime .= 'Z';
        return Time::Moment->from_string($datetime);
    };

    deflate qr/_at$/ => sub {
        my $datetime = shift;
        return $datetime->at_utc->strftime('%F %T') if blessed $datetime and $datetime->isa('Time::Moment');
        return $datetime;
    };
};

package MyProj::DB {
    use Mouse v2.4.5;
    extends qw/Aniki/;

    __PACKAGE__->setup(
        schema => 'MyProj::DB::Schema',
        filter => 'MyProj::DB::Filter',
        row    => 'MyProj::DB::Row',
    );
};

package main {
    my $db = MyProj::DB->new(connect_info => ["dbi:SQLite:dbname=:memory:", "", ""]);
    $db->execute($_) for split /;/, MyProj::DB::Schema->output;

    my $author_id = $db->insert_and_fetch_id(author => { name => 'songmu' });

    $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
};

1;

DESCRIPTION

Aniki is ORM. Lite, but powerful.

FEATURES

  • Small & Simple

    You can read codes easily.

  • Object mapping

    Inflates rows to Aniki::Result::Collection object. And inflates row to Aniki::Row object.

    You can change result class, also we can change row class. Aniki dispatches result/row class by table. (e.g. foo table to MyDB::Row::Foo)

  • Raw SQL support

    Supports to execute raw SELECT SQL and fetch rows of result. Of course, Aniki can inflate to result/row also.

  • Query builder

    Aniki includes query builder powered by SQL::Maker. SQL::Maker is fast and secure SQL builder.

  • Fork safe & Transaction support

    Aniki includes DBI handler powered by DBIx::Handler.

  • Error handling

    Easy to handle execution errors by handle_error method. You can override it.

  • Extendable

    You can extend Aniki by Mouse::Role. Aniki provides some default plugins as Mouse::Role.

RELATIONSHIP

Aniki supports relationship. Extracts relationship from schema class.

Example:

use 5.014002;
package MyProj::DB::Schema {
    use DBIx::Schema::DSL;

    create_table 'module' => columns {
        integer 'id', primary_key, auto_increment;
        varchar 'name';
        integer 'author_id';

        add_index 'author_id_idx' => ['author_id'];

        belongs_to 'author';
    };

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

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

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.

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;

SETUP

Install Aniki from CPAN:

cpanm Aniki

And run install-aniki command.

install-aniki --lib=./lib MyApp::DB

install-aniki creates skeleton modules.

METHODS

CLASS METHODS

setup(%args)

Initialize and customize Aniki class. schema is required. Others are optional.

Arguments

  • schema : ClassName
  • handler : ClassName
  • filter : ClassName
  • row : ClassName
  • result : ClassName
  • query_builder : ClassName

use_prepare_cached

If this method returns true value, Aniki uses preare_cached. This method returns true value default. So you don't need to use preare_cached, override it and return false value.

use_strict_query_builder

If this method returns true value, Aniki enables SQL::Maker's strict mode. This method returns true value default. So you need to disable SQL::Maker's strict mode, override it and return false value.

SEE ALSO: The JSON SQL Injection Vulnerability

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

    Instance of Aniki::Hanlder. If this argument is given, not required to give connect_info for arguments.

  • connect_info : ArrayRef

    Auguments for DBI's connect method.

  • on_connect_do : CodeRef|ArrayRef[Str]|Str

  • on_disconnect_do : CodeRef|ArrayRef[Str]|Str

    Execute SQL or CodeRef when connected/disconnected.

  • trace_query : Bool

    Enables to inject a caller information as SQL comment. SEE ALSO: DBIx::Handler

  • trace_ignore_if : CodeRef

    Ignore to inject the SQL comment when trace_ignore_if's return value is true. SEE ALSO: DBIx::Handler

  • suppress_row_objects : Bool

    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.

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.

And you can use there options:

  • suppress_row_objects : Bool

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

  • suppress_result_objects : Bool

    If this option is true, no create result objects. This method returns array reference instead of result object.

  • columns : ArrayRef[Str]

    List for retrieving columns from database.

  • prefetch : ArrayRef|HashRef

    Pre-fetch specified related rows. See also "RELATIONSHIP" section.

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

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

Execute 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]

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" section.

insert($table_name, \%values, \%opt)

Execute INSERT INTO query.

$db->insert(foo => { bar => 1 });
# stmt: INSERT INTO foo (bar) VALUES (?)
# bind: [1]

insert_and_fetch_id($table_name, \%values, \%opt)

Execute INSERT INTO query, and returns last_insert_id.

my $id = $db->insert_and_fetch_id(foo => { bar => 1 });
# stmt: INSERT INTO foo (bar) VALUES (?)
# bind: [1]

insert_and_fetch_row($table_name, \%values, \%opt)

Execute INSERT INTO query, and SELECT it, and returns row object.

my $row = $db->insert_and_fetch_row(foo => { bar => 1 });
# stmt: INSERT INTO foo (bar) VALUES (?)
# bind: [1]

insert_and_emulate_row($table_name, \%values, \%opt)

Execute INSERT INTO query, and returns row object created by $row and schema definition.

my $row = $db->insert_and_fetch_row(foo => { bar => 1 });
# stmt: INSERT INTO foo (bar) VALUES (?)
# bind: [1]

This method is faster than insert_and_fetch_row.

WARNING

If you use SQL TRIGGER or dynamic default value, this method don't return the correct value, maybe. In this case, you should use insert_and_fetch_row instead of this method.

insert_on_duplicate($table_name, \%insert, \%update)

Execute INSERT ... ON DUPLICATE KEY UPDATE query for MySQL.

my $row = $db->insert_on_duplicate(foo => { bar => 1 }, { bar => \'VALUE(bar) + 1' });
# stmt: INSERT INTO foo (bar) VALUES (?) ON DUPLICATE KEY UPDATE bar = VALUE(bar) + 1
# bind: [1]

SEE ALSO: INSERT ... ON DUPLICATE KEY UPDATE Syntax

insert_multi($table_name, \@values, \%opts)

Execute INSERT INTO ... (...) VALUES (...), (...), ... query for MySQL. Insert multiple rows at once.

my $row = $db->insert_multi(foo => [{ bar => 1 }, { bar => 2 }, { bar => 3 }]);
# stmt: INSERT INTO foo (bar) VALUES (?),(?),(?)
# bind: [1, 2, 3]

SEE ALSO: INSERT Syntax

update($table_name, \%set, \%where)

Execute 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]

update($row, \%set)

Execute 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]

update_and_fetch_row($row, \%set)

Execute UPDATE query, and 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]

update_and_emulate_row($row, \%set)

Execute UPDATE query, and returns row object created by $row and %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 update_and_fetch_row.

WARNING

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

delete($table_name, \%where)

Execute DELETE query, and returns changed rows count.

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.

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
  • root_row_class : Aniki::Row
  • root_result_class : Aniki::Result
  • connect_info : ArrayRef
  • on_connect_do : CodeRef|ArrayRef[Str]|Str
  • on_disconnect_do : CodeRef|ArrayRef[Str]|Str
  • suppress_row_objects : Bool
  • suppress_result_objects : Bool
  • dbh : DBI::db
  • handler : Aniki::Handler
  • txn_manager : DBIx::TransactionManager

CONTRIBUTE

I need to support documentation and reviewing my english. This module is developed on Github.

LICENSE

Copyright (C) karupanerura.

This library is free software; you can redistribute it and/or modify it under the same terms as Perl itself.

CONTRIBUTORS

  • watanabe-yocihi
  • Pine Mizune
  • Syohei YOSHIDA

AUTHOR

karupanerura karupa@cpan.org