DISCLAIMER: Image is generated using ChatGPT.
Few days ago, I shared a blog post about Introduction to DBIx::Class::Async. It was well received and got many encouraging comments about DBIx::Class::Async.
Dave Cross asked …
Do you have plans to move the async stuff into the schema or resultset classes, so you can make it more of a drop-in replacement for standard DBIC?
Daniel Bohmer asked …
Do you think you could support cursors in a future release?
Apart from that, there was some very important features missing in the first release:
- search with prefetch
- create related
As of today, we have DBIx::Class::Async v0.12 released to CPAN that addressed the above issues.
The latest release now have these packages:
- DBIx::Class::Async::Schema
- DBIx::Class::Async::ResultSet
- DBIx::Class::Async::Row
- DBIx::Class::Async::Storage
- DBIx::Class::Async::Cursor
In this post, I will try to take you through the changes.
For the demo purpose, I have created the following schema exactly how you would create for DBIx::Class:
lib/
└── MyApp
├── Schema
│ └── Result
│ ├── Post.pm
│ └── User.pm
└── Schema.pm
MyApp::Schema
package MyApp::Schema;
use strict;
use warnings;
use base 'DBIx::Class::Schema';
__PACKAGE__->load_namespaces;
1;
MyApp::Schema::Result::User
package MyApp::Schema::Result::User;
use strict;
use warnings;
use base 'DBIx::Class::Core';
__PACKAGE__->table('users');
__PACKAGE__->add_columns(
id => {
data_type => 'integer',
is_auto_increment => 1,
is_nullable => 0,
},
username => {
data_type => 'varchar',
size => 50,
is_nullable => 0,
},
email => {
data_type => 'varchar',
size => 100,
is_nullable => 0,
},
active => {
data_type => 'integer',
default_value => 1,
is_nullable => 0,
},
);
__PACKAGE__->set_primary_key('id');
__PACKAGE__->add_unique_constraint(['email']);
__PACKAGE__->add_unique_constraint(['username']);
__PACKAGE__->has_many(
'posts',
'MyApp::Schema::Result::Post',
'user_id'
);
1;
MyApp::Schema::Result::Post
package MyApp::Schema::Result::Post;
use strict;
use warnings;
use base 'DBIx::Class::Core';
__PACKAGE__->table('posts');
__PACKAGE__->add_columns(
id => {
data_type => 'integer',
is_auto_increment => 1,
is_nullable => 0,
},
user_id => {
data_type => 'integer',
is_nullable => 0,
is_foreign_key => 1,
},
title => {
data_type => 'varchar',
size => 255,
is_nullable => 0,
},
);
__PACKAGE__->set_primary_key('id');
__PACKAGE__->add_unique_constraint(['user_id', 'title']);
__PACKAGE__->belongs_to(
'user',
'MyApp::Schema::Result::User',
'user_id'
);
1;
Using DBIx::Class::Async
First I will show you some asynchronous action from DBIx::Class::Async.
Let’s create async db handler first as below:
use IO::Async::Loop;
use DBIx::Class::Async;
my $loop = IO::Async::Loop->new;
my $db = DBIx::Class::Async->new(
schema_class => 'MyApp::Schema',
connect_info => ['dbi:SQLite:dbname=myapp.db', undef, undef, {}],
workers => 2,
loop => $loop,
);
Now I will create a user and a post for the newly created user.
my $user = await $db->create(
'User',
{ username => 'New User', email => 'new@example.com' }
);
my $user_id = $user->{id};
my $post = await $db->create(
'Post',
{ user_id => $user_id,
title => 'My First Post',
});
my $post_id = $post->{id};
say "User Id: $user_id";
say "Post Id: $post_id";
NOTE: The DBIx::Class::Async returns a hash for the newly create object.
I am using keyword await for each operation and for this you will have to use Future::AsyncAwait;.
You can checkout other functions as documented in the pod document. I want to focus more on the updates to DBIx::Class::Async in this post.
Using DBIx::Class::Async::Schema
Here I will create async schema as below:
use IO::Async::Loop;
use DBIx::Class::Async::Schema;
my $loop = IO::Async::Loop->new;
my $schema = DBIx::Class::Async::Schema->connect(
"dbi:SQLite:dbname=myapp.db", undef, undef, {},
{
workers => 2,
schema_class => 'MyApp::Schema',
loop => $loop
}
);
I will now create user and post like below:
my $user = await $schema->resultset('User')
->create({
username => 'Test User',
email => 'test@example.com',
active => 1,
});
my $post = await $schema->resultset('Post')
->create({
user_id => $user->id,
title => 'My First Post' })
->then(sub {
my ($row) = shift;
return Future->done($row);
});
say $user->id;
say $post->id;
I showed two ways to perform similar task. You pick your preferred choice.
Here $user is an object of type DBIx::Class::Async::Row::User and $post is an object of type DBIx::Class::Async::Row::Post.
This is a big improvement (imho).
Let’s do the same using create_related function.
my $user = await $schema->resultset('User')
->create({
username => 'john_doe',
email => 'john@example.com',
active => 1,
});
my $post = await $user->create_related('posts',
{
title => 'My First Post',
});
say $user->id;
say $post->id;
I will now show you prefetch function.
my $user = await $schema->resultset('User')
->prefetch('posts')
->find({ active => 1 });
my $posts = $user->posts->all->get;
foreach my $post (@$posts) {
say $post->title;
}
and one more variant of prefetch as below:
my $posts = $schema->resultset('Post')
->search(
{},
{ prefetch => 'user' }
)->all->get;
foreach my $post (@$posts) {
say $post->user->get->id;
say $post->title;
}
Let me show you next function.
my $user = $schema->resultset('User')
->search({ active => 1 });
while (my $row = $user->next) {
say "User Id:", $row->get_column('id');
}
Finally cursor in action as below:
my $cursor = $schema->resultset('User')->cursor;
while (my $user = await $cursor->next) {
say "Processing user: " . $user->id;
#
# Do some async work with the user
#
}
This is still experimental but I would love to hear your views. I am open to suggestions.
Happy Hacking !!!