Skip to content

Commit

Permalink
add asynchronous backend api using promises
Browse files Browse the repository at this point in the history
Now all backends support a promises API. The backends that have
asynchronous options will use promises asynchronously. The ones that
don't will return an already-fulfilled promise.
  • Loading branch information
preaction committed Aug 12, 2018
1 parent 591be39 commit db4a4d1
Show file tree
Hide file tree
Showing 7 changed files with 292 additions and 36 deletions.
74 changes: 72 additions & 2 deletions lib/Yancy/Backend.pod
Expand Up @@ -18,7 +18,7 @@
A C<Yancy::Backend> handles talking to the database. Different Yancy
backends will support different databases. To use a backend, see
L</SUPPORTED BACKENDS>. To make your own backend, see L</METHODS> for
the list of methods you must implement, their arguments, and their
the list of methods each backend supports, their arguments, and their
return values.

=head2 Terminology
Expand All @@ -27,6 +27,16 @@ Yancy backends work with collections, which are made up of items.
A collection is a set of items, like a database table. An item is
a single element of a collection, and must be a hashref.

=head2 Asynchronous Backends

Asynchronous backends implement both a synchronous and an asynchronous
API (using promises).

=head2 Synchronous-only Backends

Synchronous-only backends also implement a promises API for
compatibility, but will not perform requests concurrently.

=head1 SUPPORTED BACKENDS

=over
Expand Down Expand Up @@ -105,22 +115,57 @@ C<offset>.

=back

=head2 list_p

my $promise = $be->list_p( $collection, $where, $opt );
$promise->then( sub {
my ( $result ) = @_;
# { total => ..., items => [ ... ] }
} );

Fetch a list of items asynchronously using promises. Returns a promise that
resolves to a hashref with C<items> and C<total> keys. See L</list> for
arguments and return values.

=head2 get

my $item = $be->get( $collection, $id );

Get a single item. C<$collection> is the collection name. C<$id> is the
ID of the item to get. Returns a hashref of item data.

=head2 get_p

my $promise = $be->get_p( $collection, $id );
$promise->then( sub {
my ( $item ) = @_;
# ...
} );

Get a single item asynchronously using promises. Returns a promise that
resolves to the item. See L</get> for arguments and return values.

=head2 set

$be->set( $collection, $id, $item );
my $success = $be->set( $collection, $id, $item );

Update an item. C<$collection> is the collection name. C<$id> is the ID
of the item to update. C<$item> is the item's data to set. Returns
a boolean that is true if a row with the given ID was found and updated,
false otherwise.

=head2 set_p

my $promise = $be->set_p( $collection, $id );
$promise->then( sub {
my ( $success ) = @_;
# ...
} );

Update a single item asynchronously using promises. Returns a promise
that resolves to a boolean indicating if the row was updated. See
L</set> for arguments and return values.

=head2 create

my $id = $be->create( $collection, $item );
Expand All @@ -129,6 +174,18 @@ Create a new item. C<$collection> is the collection name. C<$item> is
the item's data. Returns the ID of the row created suitable to be passed
in to C<the get() method|/get>.

=head2 create_p

my $promise = $be->create_p( $collection, $item );
$promise->then( sub {
my ( $id ) = @_;
# ...
} );

Create a new item asynchronously using promises. Returns a promise that
resolves to the ID of the newly-created item. See L</create> for
arguments and return values.

=head2 delete

$be->delete( $collection, $id );
Expand All @@ -137,9 +194,22 @@ Delete an item. C<$collection> is the collection name. C<$id> is the ID
of the item to delete. Returns a boolean that is true if a row with the
given ID was found and deleted. False otherwise.

=head2 delete_p

my $promise = $be->delete_p( $collection, $id );
$promise->then( sub {
my ( $success ) = @_;
# ...
} );

Delete an item asynchronously using promises. Returns a promise that
resolves to a boolean indicating if the row was deleted. See L</delete>
for arguments and return values.

=head2 read_schema

my $schema = $be->read_schema;

Read the schema from the database tables. Returns an OpenAPI schema
ready to be merged into the user's configuration.

2 changes: 2 additions & 0 deletions lib/Yancy/Backend/Dbic.pm
Expand Up @@ -103,6 +103,8 @@ L<Yancy::Backend>, L<DBIx::Class>, L<Yancy>
=cut

use Mojo::Base '-base';
use Role::Tiny qw( with );
with 'Yancy::Backend::Role::Sync';
use Scalar::Util qw( looks_like_number blessed );
use Mojo::Loader qw( load_class );

Expand Down
75 changes: 66 additions & 9 deletions lib/Yancy/Backend/Mysql.pm
Expand Up @@ -111,10 +111,11 @@ L<Mojo::mysql>, L<Yancy>
=cut

use Mojo::Base '-base';
use Mojo::Promise;
use Scalar::Util qw( blessed looks_like_number );
BEGIN {
eval { require Mojo::mysql; Mojo::mysql->VERSION( 1 ); 1 }
or die "Could not load Mysql backend: Mojo::mysql version 1 or higher required\n";
eval { require Mojo::mysql; Mojo::mysql->VERSION( 1.05 ); 1 }
or die "Could not load Mysql backend: Mojo::mysql version 1.05 or higher required\n";
}

has mysql =>;
Expand All @@ -136,24 +137,43 @@ sub new {
return $class->SUPER::new( %vars );
}

sub _id_field {
my ( $self, $coll ) = @_;
return $self->collections->{ $coll }{ 'x-id-field' } || 'id';
}

sub create {
my ( $self, $coll, $params ) = @_;
my $id_field = $self->collections->{ $coll }{ 'x-id-field' } || 'id';
my $id_field = $self->_id_field( $coll );
my $id = $self->mysql->db->insert( $coll, $params )->last_insert_id;
# Assume the id field is correct in case we're using a different
# unique ID (not the auto-increment column).
return $params->{ $id_field } || $id;
}

sub create_p {
my ( $self, $coll, $params ) = @_;
my $id_field = $self->_id_field( $coll );
return $self->mysql->db->insert_p( $coll, $params )
->then( sub { $params->{ $id_field } || shift->last_insert_id } );
}

sub get {
my ( $self, $coll, $id ) = @_;
my $id_field = $self->collections->{ $coll }{ 'x-id-field' } || 'id';
my $id_field = $self->_id_field( $coll );
return $self->mysql->db->select( $coll, undef, { $id_field => $id } )->hash;
}

sub list {
sub get_p {
my ( $self, $coll, $id ) = @_;
my $id_field = $self->_id_field( $coll );
my $db = $self->mysql->db;
return $db->select_p( $coll, undef, { $id_field => $id } )
->then( sub { shift->hash } );
}

sub _list_sqls {
my ( $self, $coll, $params, $opt ) = @_;
$params ||= {}; $opt ||= {};
my $mysql = $self->mysql;
my ( $query, @params ) = $mysql->abstract->select( $coll, undef, $params, $opt->{order_by} );
my ( $total_query, @total_params ) = $mysql->abstract->select( $coll, [ \'COUNT(*) as total' ], $params );
Expand All @@ -166,24 +186,61 @@ sub list {
}
}
#; say $query;
return ( $query, $total_query, @params );
}

sub list {
my ( $self, $coll, $params, $opt ) = @_;
$params ||= {}; $opt ||= {};
my $mysql = $self->mysql;
my ( $query, $total_query, @params ) = $self->_list_sqls( $coll, $params, $opt );
return {
items => $mysql->db->query( $query, @params )->hashes,
total => $mysql->db->query( $total_query, @total_params )->hash->{total},
total => $mysql->db->query( $total_query, @params )->hash->{total},
};
}

sub list_p {
my ( $self, $coll, $params, $opt ) = @_;
$params ||= {}; $opt ||= {};
my $mysql = $self->mysql;
my ( $query, $total_query, @params ) = $self->_list_sqls( $coll, $params, $opt );
my $items_p = $mysql->db->query_p( $query, @params )->then( sub { shift->hashes } );
my $total_p = $mysql->db->query_p( $total_query, @params )
->then( sub { shift->hash->{total} } );
return Mojo::Promise->all( $items_p, $total_p )
->then( sub {
my ( $items, $total ) = @_;
return { items => $items->[0], total => $total->[0] };
} );
}

sub set {
my ( $self, $coll, $id, $params ) = @_;
my $id_field = $self->collections->{ $coll }{ 'x-id-field' } || 'id';
my $id_field = $self->_id_field( $coll );
return !!$self->mysql->db->update( $coll, $params, { $id_field => $id } )->rows;
}

sub set_p {
my ( $self, $coll, $id, $params ) = @_;
my $id_field = $self->_id_field( $coll );
return $self->mysql->db->update_p( $coll, $params, { $id_field => $id } )
->then( sub { !!shift->rows } );
}

sub delete {
my ( $self, $coll, $id ) = @_;
my $id_field = $self->collections->{ $coll }{ 'x-id-field' } || 'id';
my $id_field = $self->_id_field( $coll );
return !!$self->mysql->db->delete( $coll, { $id_field => $id } )->rows;
}

sub delete_p {
my ( $self, $coll, $id ) = @_;
my $id_field = $self->_id_field( $coll );
return $self->mysql->db->delete_p( $coll, { $id_field => $id } )
->then( sub { !!shift->rows } );
}

sub read_schema {
my ( $self ) = @_;
my $database = $self->mysql->db->query( 'SELECT DATABASE()' )->array->[0];
Expand Down
74 changes: 65 additions & 9 deletions lib/Yancy/Backend/Pg.pm
Expand Up @@ -112,10 +112,11 @@ L<Mojo::Pg>, L<Yancy>
=cut

use Mojo::Base '-base';
use Mojo::Promise;
use Scalar::Util qw( looks_like_number blessed );
BEGIN {
eval { require Mojo::Pg; Mojo::Pg->VERSION( 3 ); 1 }
or die "Could not load Pg backend: Mojo::Pg version 3 or higher required\n";
eval { require Mojo::Pg; Mojo::Pg->VERSION( 4.03 ); 1 }
or die "Could not load Pg backend: Mojo::Pg version 4.03 or higher required\n";
}

has pg =>;
Expand All @@ -141,21 +142,40 @@ sub new {
return $class->SUPER::new( %vars );
}

sub _id_field {
my ( $self, $coll ) = @_;
return $self->collections->{ $coll }{ 'x-id-field' } || 'id';
}

sub create {
my ( $self, $coll, $params ) = @_;
my $id_field = $self->collections->{ $coll }{ 'x-id-field' } || 'id';
my $id_field = $self->_id_field( $coll );
return $self->pg->db->insert( $coll, $params, { returning => $id_field } )->hash->{ $id_field };
}

sub create_p {
my ( $self, $coll, $params ) = @_;
my $id_field = $self->_id_field( $coll );
return $self->pg->db->insert_p( $coll, $params, { returning => $id_field } )
->then( sub { shift->hash->{ $id_field } } );
}

sub get {
my ( $self, $coll, $id ) = @_;
my $id_field = $self->collections->{ $coll }{ 'x-id-field' } || 'id';
my $id_field = $self->_id_field( $coll );
return $self->pg->db->select( $coll, undef, { $id_field => $id } )->hash;
}

sub list {
sub get_p {
my ( $self, $coll, $id ) = @_;
my $id_field = $self->_id_field( $coll );
my $db = $self->pg->db;
return $db->select_p( $coll, undef, { $id_field => $id } )
->then( sub { shift->hash } );
}

sub _list_sqls {
my ( $self, $coll, $params, $opt ) = @_;
$params ||= {}; $opt ||= {};
my $pg = $self->pg;
my ( $query, @params ) = $pg->abstract->select( $coll, undef, $params, $opt->{order_by} );
my ( $total_query, @total_params ) = $pg->abstract->select( $coll, [ \'COUNT(*) as total' ], $params );
Expand All @@ -168,25 +188,61 @@ sub list {
}
}
#; say $query;
return ( $query, $total_query, @params );
}

sub list {
my ( $self, $coll, $params, $opt ) = @_;
$params ||= {}; $opt ||= {};
my $pg = $self->pg;
my ( $query, $total_query, @params ) = $self->_list_sqls( $coll, $params, $opt );
return {
items => $pg->db->query( $query, @params )->hashes,
total => $pg->db->query( $total_query, @total_params )->hash->{total},
total => $pg->db->query( $total_query, @params )->hash->{total},
};
}

sub list_p {
my ( $self, $coll, $params, $opt ) = @_;
$params ||= {}; $opt ||= {};
my $pg = $self->pg;
my ( $query, $total_query, @params ) = $self->_list_sqls( $coll, $params, $opt );
my $items_p = $pg->db->query_p( $query, @params )->then( sub { shift->hashes } );
my $total_p = $pg->db->query_p( $total_query, @params )
->then( sub { shift->hash->{total} } );
return Mojo::Promise->all( $items_p, $total_p )
->then( sub {
my ( $items, $total ) = @_;
return { items => $items->[0], total => $total->[0] };
} );
}

sub set {
my ( $self, $coll, $id, $params ) = @_;
my $id_field = $self->collections->{ $coll }{ 'x-id-field' } || 'id';
my $id_field = $self->_id_field( $coll );
return !!$self->pg->db->update( $coll, $params, { $id_field => $id } )->rows;
}

sub set_p {
my ( $self, $coll, $id, $params ) = @_;
my $id_field = $self->_id_field( $coll );
return $self->pg->db->update_p( $coll, $params, { $id_field => $id } )
->then( sub { !!shift->rows } );
}

sub delete {
my ( $self, $coll, $id ) = @_;
my $id_field = $self->collections->{ $coll }{ 'x-id-field' } || 'id';
my $id_field = $self->_id_field( $coll );
return !!$self->pg->db->delete( $coll, { $id_field => $id } )->rows;
}

sub delete_p {
my ( $self, $coll, $id ) = @_;
my $id_field = $self->_id_field( $coll );
return $self->pg->db->delete_p( $coll, { $id_field => $id } )
->then( sub { !!shift->rows } );
}

sub read_schema {
my ( $self ) = @_;
my $database = $self->pg->db->query( 'SELECT current_schema()' )->array->[0];
Expand Down

0 comments on commit db4a4d1

Please sign in to comment.