Aniki - The ORM as our great brother.
use 5.014002;
package MyProj::DB::Schema {
use DBIx::Schema::DSL;
database 'SQLite';
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',
);
};
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;
Aniki is ORM. Lite, but powerful.
-
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 toMyDB::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.
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;
Install Aniki from CPAN:
cpanm Aniki
And run install-aniki
command.
install-aniki --lib=./lib MyApp::DB
install-aniki
creates skeleton modules.
Initialize and customize Aniki class.
schema
is required. Others are optional.
- schema : ClassName
- handler : ClassName
- filter : ClassName
- row : ClassName
- result : ClassName
- query_builder : ClassName
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.
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 result classes.
Guesses result class by table name.
Guesses row class by table name.
Create instance of Aniki.
-
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.
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]
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.
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]
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.
Execute INSERT INTO
query.
$db->insert(foo => { bar => 1 });
# stmt: INSERT INTO foo (bar) VALUES (?)
# bind: [1]
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]
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]
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
.
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.
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
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
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]
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]
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]
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
.
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.
Execute DELETE
query, and returns changed rows count.
my $count = $db->delete(foo => { id => 1 });
# stmt: DELETE FROM foo WHERE id = ?
# bind: [1]
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]
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
I need to support documentation and reviewing my english. This module is developed on Github.
Copyright (C) karupanerura.
This library is free software; you can redistribute it and/or modify it under the same terms as Perl itself.
- watanabe-yocihi
- Pine Mizune
- Syohei YOSHIDA
karupanerura karupa@cpan.org