diff --git a/lib/Mango.pm b/lib/Mango.pm index b0aad1e..3f0541a 100644 --- a/lib/Mango.pm +++ b/lib/Mango.pm @@ -5,6 +5,7 @@ use Carp 'croak'; use Hash::Util::FieldHash; use Mango::BSON 'bson_doc'; use Mango::Database; +use Mango::Promises; use Mango::Protocol; use Mojo::IOLoop; use Mojo::URL; @@ -13,6 +14,7 @@ use Scalar::Util 'weaken'; use constant DEBUG => $ENV{MANGO_DEBUG} || 0; use constant DEFAULT_PORT => 27017; +use constant PROMISES => Mango::Promises::PROMISES; has connect_opt => sub { [] }; has default_db => 'admin'; @@ -31,6 +33,8 @@ Hash::Util::FieldHash::fieldhash my %AUTH; our $VERSION = '1.30'; +Mango::Promises->generate_p_methods(qw(get_more kill_cursors/0 query)); + sub DESTROY { shift->_cleanup } sub backlog { scalar @{shift->{queue} || []} } @@ -406,6 +410,13 @@ you can use to generate data types that are not available natively in Perl. All connections will be reset automatically if a new process has been forked, this allows multiple processes to share the same L object safely. +Since version 1.31, L supports non-blocking with promise-returning +methods like +L<< $collection->find_one_p|Mango::Collection/"find_one_p" >>, +L<< $cursor->all_p|Mango::Cursor/"all_p" >>, +L<< $bulk->execute_p|Mango::Bulk/"execute_p" >>, etc. +Note that promise support depends on L (L 7.53+). + For better scalability (epoll, kqueue) and to provide IPv6, SOCKS5 as well as TLS support, the optional modules L (4.0+), L (0.20+), L (0.64+) and L (1.84+) will be used @@ -554,6 +565,15 @@ perform operation non-blocking. }); Mojo::IOLoop->start unless Mojo::IOLoop->is_running; +=head2 get_more_p + + my $promise = $mango->get_more_p($namespace, $return, $cursor); + +Same as L, but performs a non-blocking operation +and returns a L object instead of accepting a callback. + +Notice that promise support depends on L (L 7.53+). + =head2 kill_cursors $mango->kill_cursors(@ids); @@ -567,6 +587,15 @@ perform operation non-blocking. }); Mojo::IOLoop->start unless Mojo::IOLoop->is_running; +=head2 kill_cursors_p + + my $promise = $mango->kill_cursors_p(@ids); + +Same as L, but performs a non-blocking operation +and returns a L object instead of accepting a callback. + +Notice that promise support depends on L (L 7.53+). + =head2 new my $mango = Mango->new; @@ -607,6 +636,16 @@ perform operation non-blocking. }); Mojo::IOLoop->start unless Mojo::IOLoop->is_running; +=head2 query_p + + my $promise + = $mango->query_p($namespace, $flags, $skip, $return, $query, $fields); + +Same as L, but performs a non-blocking operation +and returns a L object instead of accepting a callback. + +Notice that promise support depends on L (L 7.53+). + =head1 DEBUGGING You can set the C environment variable to get some advanced diff --git a/lib/Mango/Bulk.pm b/lib/Mango/Bulk.pm index a89c03b..ca6d35b 100644 --- a/lib/Mango/Bulk.pm +++ b/lib/Mango/Bulk.pm @@ -3,11 +3,14 @@ use Mojo::Base -base; use Carp 'croak'; use Mango::BSON qw(bson_doc bson_encode bson_oid bson_raw); +use Mango::Promises; use Mojo::IOLoop; has 'collection'; has ordered => 1; +Mango::Promises->generate_p_methods(qw(execute)); + sub execute { my ($self, $cb) = @_; @@ -209,6 +212,15 @@ non-blocking. }); Mojo::IOLoop->start unless Mojo::IOLoop->is_running; +=head2 execute_p + + my $promise = $bulk->execute_p; + +Same as L, but performs bulk operations non-blocking +and returns a L object instead of accepting a callback. + +Notice that promise support depends on L (L 7.53+). + =head2 find $bulk = $bulk->find({foo => 'bar'}); diff --git a/lib/Mango/Collection.pm b/lib/Mango/Collection.pm index d1ef5f3..ff6898c 100644 --- a/lib/Mango/Collection.pm +++ b/lib/Mango/Collection.pm @@ -6,9 +6,15 @@ use Mango::BSON qw(bson_code bson_doc bson_oid); use Mango::Bulk; use Mango::Cursor; use Mango::Cursor::Query; +use Mango::Promises; has [qw(db name)]; +Mango::Promises->generate_p_methods( + qw(aggregate create/0 drop/0 drop_index/0 ensure_index/0 find_and_modify find_one + index_information insert map_reduce options remove rename save stats update) +); + sub aggregate { my ($self, $pipeline) = (shift, shift); my $cb = ref $_[-1] eq 'CODE' ? pop : undef; @@ -328,6 +334,15 @@ operation non-blocking. }); Mojo::IOLoop->start unless Mojo::IOLoop->is_running; +=head2 aggregate_p + + my $promise = $collection->aggregate_p(\@pipeline); + +Same as L, but performs a non-blocking operation +and returns a L object instead of accepting a callback. + +Notice that promise support depends on L (L 7.53+). + =head2 build_index_name my $name = $collection->build_index_name(bson_doc(foo => 1, bar => -1)); @@ -362,6 +377,16 @@ non-blocking. }); Mojo::IOLoop->start unless Mojo::IOLoop->is_running; +=head2 create_p + + my $promise = $collection->create_p; + my $promise = $collection->create_p(\%opts); + +Same as L, but performs a non-blocking operation +and returns a L object instead of accepting a callback. + +Notice that promise support depends on L (L 7.53+). + =head2 drop $collection->drop; @@ -375,6 +400,15 @@ non-blocking. }); Mojo::IOLoop->start unless Mojo::IOLoop->is_running; +=head2 drop_p + + my $promise = $collection->drop_p; + +Same as L, but performs a non-blocking operation +and returns a L object instead of accepting a callback. + +Notice that promise support depends on L (L 7.53+). + =head2 drop_index $collection->drop_index('foo'); @@ -387,6 +421,15 @@ Drop index. You can also append a callback to perform operation non-blocking. }); Mojo::IOLoop->start unless Mojo::IOLoop->is_running; +=head2 drop_index_p + + my $promise = $collection->drop_index_p; + +Same as L, but performs a non-blocking operation +and returns a L object instead of accepting a callback. + +Notice that promise support depends on L (L 7.53+). + =head2 ensure_index $collection->ensure_index(bson_doc(foo => 1, bar => -1)); @@ -403,6 +446,17 @@ append a callback to perform operation non-blocking. }); Mojo::IOLoop->start unless Mojo::IOLoop->is_running; +=head2 ensure_index_p + + $promise = $collection->ensure_index_p(bson_doc(foo => 1, bar => -1)); + $promise = $collection->ensure_index_p({foo => 1}); + $promise = $collection->ensure_index_p({foo => 1}, {unique => bson_true}); + +Same as L, but performs a non-blocking operation +and returns a L object instead of accepting a callback. + +Notice that promise support depends on L (L 7.53+). + =head2 find my $cursor = $collection->find; @@ -432,6 +486,15 @@ to perform operation non-blocking. By default this method returns the unmodified version of the document. To change this behaviour, add the option C 1>. +=head2 find_and_modify_p + + $promise = $collection->find_and_modify_p($opts); + +Same as L, but performs a non-blocking operation +and returns a L object instead of accepting a callback. + +Notice that promise support depends on L (L 7.53+). + =head2 find_one my $doc = $collection->find_one({foo => 'bar'}); @@ -447,6 +510,17 @@ non-blocking. }); Mojo::IOLoop->start unless Mojo::IOLoop->is_running; +=head2 find_one_p + + my $promise = $collection->find_one_p({foo => 'bar'}); + my $promise = $collection->find_one_p({foo => 'bar'}, {foo => 1}); + my $promise = $collection->find_one_p($oid, {foo => 1}); + +Same as L, but performs a non-blocking operation +and returns a L object instead of accepting a callback. + +Notice that promise support depends on L (L 7.53+). + =head2 full_name my $name = $collection->full_name; @@ -468,6 +542,16 @@ perform operation non-blocking. }); Mojo::IOLoop->start unless Mojo::IOLoop->is_running; +=head2 index_information_p + + my $promise = $collection->index_information_p; + my $promise = $collection->index_information_p(cursor => { batchSize => 5 }); + +Same as L, but performs a non-blocking operation +and returns a L object instead of accepting a callback. + +Notice that promise support depends on L (L 7.53+). + =head2 insert my $oid = $collection->insert({foo => 'bar'}); @@ -487,6 +571,16 @@ them to MongoDB. To avoid modifying your data, it makes a copy of the documents. This can be a bit slow if you are sending big objects like pictures. To avoid that, consider using C instead. +=head2 insert_p + + my $promise = $collection->insert_p({foo => 'bar'}); + my $promise = $collection->insert_p([{foo => 'bar'}, {baz => 'yada'}]); + +Same as L, but performs a non-blocking operation +and returns a L object instead of accepting a callback. + +Notice that promise support depends on L (L 7.53+). + =head2 map_reduce my $collection = $collection->map_reduce($map, $reduce, {out => 'foo'}); @@ -505,6 +599,18 @@ operation non-blocking. ); Mojo::IOLoop->start unless Mojo::IOLoop->is_running; +=head2 map_reduce_p + + my $promise = $collection->map_reduce_p($map, $reduce, {out => 'foo'}); + my $promise = $collection->map_reduce_p($map, $reduce, {out => {inline => 1}}); + my $promise = $collection->map_reduce_p( + bson_code($map), bson_code($reduce), {out => {inline => 1}}); + +Same as L, but performs a non-blocking operation +and returns a L object instead of accepting a callback. + +Notice that promise support depends on L (L 7.53+). + =head2 options my $doc = $collection->options; @@ -518,6 +624,15 @@ operation non-blocking. }); Mojo::IOLoop->start unless Mojo::IOLoop->is_running; +=head2 options_p + + my $promise = $collection->options_p; + +Same as L, but performs a non-blocking operation +and returns a L object instead of accepting a callback. + +Notice that promise support depends on L (L 7.53+). + =head2 remove my $result = $collection->remove; @@ -546,6 +661,18 @@ Remove only one document. =back +=head2 remove_p + + my $promise = $collection->remove_p; + my $promise = $collection->remove_p($oid); + my $promise = $collection->remove_p({foo => 'bar'}); + my $promise = $collection->remove_p({foo => 'bar'}, {single => 1}); + +Same as L, but performs a non-blocking operation +and returns a L object instead of accepting a callback. + +Notice that promise support depends on L (L 7.53+). + =head2 rename my $new_collection = $collection->rename('NewName'); @@ -560,6 +687,15 @@ also append a callback to perform operation non-blocking. }); Mojo::IOLoop->start unless Mojo::IOLoop->is_running; +=head2 rename_p + + my $promise = $collection->rename_p('NewName'); + +Same as L, but performs a non-blocking operation +and returns a L object instead of accepting a callback. + +Notice that promise support depends on L (L 7.53+). + =head2 save my $oid = $collection->save({foo => 'bar'}); @@ -573,6 +709,15 @@ append a callback to perform operation non-blocking. }); Mojo::IOLoop->start unless Mojo::IOLoop->is_running; +=head2 save_p + + my $promise = $collection->save_p({foo => 'bar'}); + +Same as L, but performs a non-blocking operation +and returns a L object instead of accepting a callback. + +Notice that promise support depends on L (L 7.53+). + =head2 stats my $stats = $collection->stats; @@ -586,6 +731,15 @@ non-blocking. }); Mojo::IOLoop->start unless Mojo::IOLoop->is_running; +=head2 stats_p + + my $promise = $collection->stats_p; + +Same as L, but performs a non-blocking operation +and returns a L object instead of accepting a callback. + +Notice that promise support depends on L (L 7.53+). + =head2 update my $result = $collection->update($oid, {foo => 'baz'}); @@ -619,6 +773,17 @@ Insert document if none could be updated. =back +=head2 update_p + + my $promise = $collection->update_p($oid, {foo => 'baz'}); + my $promise = $collection->update_p({foo => 'bar'}, {foo => 'baz'}); + my $promise = $collection->update_p({foo => 'bar'}, {foo => 'baz'}, {multi => 1}); + +Same as L, but performs a non-blocking operation +and returns a L object instead of accepting a callback. + +Notice that promise support depends on L (L 7.53+). + =head1 SEE ALSO L, L, L. diff --git a/lib/Mango/Cursor.pm b/lib/Mango/Cursor.pm index fcc51ab..913eeee 100644 --- a/lib/Mango/Cursor.pm +++ b/lib/Mango/Cursor.pm @@ -1,11 +1,14 @@ package Mango::Cursor; use Mojo::Base -base; +use Mango::Promises; use Mojo::IOLoop; has [qw(collection id ns)]; has [qw(batch_size limit)] => 0; +Mango::Promises->generate_p_methods(qw(all next rewind/0)); + sub add_batch { my ($self, $docs) = @_; push @{$self->{results} ||= []}, @$docs; @@ -185,6 +188,15 @@ operation non-blocking. }); Mojo::IOLoop->start unless Mojo::IOLoop->is_running; +=head2 all_p + + my $promise = $cursor->all_p; + +Same as L, but performs a non-blocking operation +and returns a L object instead of accepting a callback. + +Notice that promise support depends on L (L 7.53+). + =head2 next my $doc = $cursor->next; @@ -198,6 +210,15 @@ non-blocking. }); Mojo::IOLoop->start unless Mojo::IOLoop->is_running; +=head2 next_p + + my $promise = $cursor->next_p; + +Same as L, but performs a non-blocking operation +and returns a L object instead of accepting a callback. + +Notice that promise support depends on L (L 7.53+). + =head2 rewind $cursor->rewind; @@ -211,6 +232,15 @@ perform operation non-blocking. }); Mojo::IOLoop->start unless Mojo::IOLoop->is_running; +=head2 rewind_p + + my $promise = $cursor->rewind_p; + +Same as L, but performs a non-blocking operation +and returns a L object instead of accepting a callback. + +Notice that promise support depends on L (L 7.53+). + =head2 num_to_return my $num = $cursor->num_to_return; diff --git a/lib/Mango/Cursor/Query.pm b/lib/Mango/Cursor/Query.pm index c5386dc..052d8d8 100644 --- a/lib/Mango/Cursor/Query.pm +++ b/lib/Mango/Cursor/Query.pm @@ -2,6 +2,7 @@ package Mango::Cursor::Query; use Mojo::Base 'Mango::Cursor'; use Mango::BSON 'bson_doc'; +use Mango::Promises; has [ qw(await_data comment hint max_scan max_time_ms read_preference snapshot), @@ -10,6 +11,8 @@ has [ has [qw(fields query)]; has skip => 0; +Mango::Promises->generate_p_methods(qw(count distinct explain)); + sub build_query { my ($self, $explain) = @_; @@ -253,6 +256,15 @@ callback to perform operation non-blocking. }); Mojo::IOLoop->start unless Mojo::IOLoop->is_running; +=head2 count_p + + my $promise = $cursor->count_p; + +Same as L, but performs a non-blocking operation +and returns a L object instead of accepting a callback. + +Notice that promise support depends on L (L 7.53+). + =head2 distinct my $values = $cursor->distinct('foo'); @@ -266,6 +278,15 @@ operation non-blocking. }); Mojo::IOLoop->start unless Mojo::IOLoop->is_running; +=head2 distinct_p + + my $promise = $cursor->distinct_p('foo'); + +Same as L, but performs a non-blocking operation +and returns a L object instead of accepting a callback. + +Notice that promise support depends on L (L 7.53+). + =head2 explain my $doc = $cursor->explain; @@ -279,6 +300,15 @@ perform operation non-blocking. }); Mojo::IOLoop->start unless Mojo::IOLoop->is_running; +=head2 explain_p + + my $promise = $cursor->explain_p; + +Same as L, but performs a non-blocking operation +and returns a L object instead of accepting a callback. + +Notice that promise support depends on L (L 7.53+). + =head1 SEE ALSO L, L, L. diff --git a/lib/Mango/Database.pm b/lib/Mango/Database.pm index 4b0ea03..54e04c5 100644 --- a/lib/Mango/Database.pm +++ b/lib/Mango/Database.pm @@ -5,9 +5,13 @@ use Carp 'croak'; use Mango::BSON qw(bson_code bson_doc); use Mango::Collection; use Mango::GridFS; +use Mango::Promises; has [qw(mango name)]; +Mango::Promises->generate_p_methods( + qw(collection_names command dereference list_collections stats)); + sub build_write_concern { my $mango = shift->mango; return { @@ -175,6 +179,15 @@ to perform operation non-blocking. }); Mojo::IOLoop->start unless Mojo::IOLoop->is_running; +=head2 collection_names_p + + my $promise = $db->collection_names_p; + +Same as L, but performs a non-blocking operation +and returns a L object instead of accepting a callback. + +Notice that promise support depends on L (L 7.53+). + =head2 command my $doc = $db->command(bson_doc(text => 'foo.bar', search => 'test')); @@ -190,6 +203,17 @@ non-blocking. }); Mojo::IOLoop->start unless Mojo::IOLoop->is_running; +=head2 command_p + + my $promise = $db->command_p(bson_doc(text => 'foo.bar', search => 'test')); + my $promise = $db->command_p(bson_doc(getLastError => 1, w => 2)); + my $promise = $db->command_p('getLastError', w => 2); + +Same as L, but performs a non-blocking operation +and returns a L object instead of accepting a callback. + +Notice that promise support depends on L (L 7.53+). + =head2 dereference my $doc = $db->dereference($dbref); @@ -203,6 +227,15 @@ operation non-blocking. }); Mojo::IOLoop->start unless Mojo::IOLoop->is_running; +=head2 dereference_p + + my $promise = $db->dereference_p($dbref); + +Same as L, but performs a non-blocking operation +and returns a L object instead of accepting a callback. + +Notice that promise support depends on L (L 7.53+). + =head2 gridfs my $gridfs = $db->gridfs; @@ -230,6 +263,18 @@ C. You can also append a callback to perform operation non-blocking. }); Mojo::IOLoop->start unless Mojo::IOLoop->is_running; +=head2 list_collections_p + + my $promise = $db->list_collections_p; + my $promise = $db->list_collections_p(filter => { name => qr{^prefix} }); + my $promise = $db->list_collections_p(filter => { 'options.capped' => 1 }); + my $promise = $db->list_collections_p(cursor => { batchSize => 10 }); + +Same as L, but performs a non-blocking operation +and returns a L object instead of accepting a callback. + +Notice that promise support depends on L (L 7.53+). + =head2 stats my $stats = $db->stats; @@ -243,6 +288,15 @@ non-blocking. }); Mojo::IOLoop->start unless Mojo::IOLoop->is_running; +=head2 stats_p + + my $promise = $db->stats_p; + +Same as L, but performs a non-blocking operation +and returns a L object instead of accepting a callback. + +Notice that promise support depends on L (L 7.53+). + =head1 SEE ALSO L, L, L. diff --git a/lib/Mango/GridFS.pm b/lib/Mango/GridFS.pm index c3cea3d..3476960 100644 --- a/lib/Mango/GridFS.pm +++ b/lib/Mango/GridFS.pm @@ -3,12 +3,15 @@ use Mojo::Base -base; use Mango::GridFS::Reader; use Mango::GridFS::Writer; +use Mango::Promises; has chunks => sub { $_[0]->db->collection($_[0]->prefix . '.chunks') }; has 'db'; has files => sub { $_[0]->db->collection($_[0]->prefix . '.files') }; has prefix => 'fs'; +Mango::Promises->generate_p_methods(qw(delete/0 find_version list)); + sub delete { my ($self, $oid, $cb) = @_; @@ -129,6 +132,15 @@ Delete file. You can also append a callback to perform operation non-blocking. }); Mojo::IOLoop->start unless Mojo::IOLoop->is_running; +=head2 delete_p + + my $promise = $gridfs->delete_p($oid); + +Same as L, but performs a non-blocking operation +and returns a L object instead of accepting a callback. + +Notice that promise support depends on L (L 7.53+). + =head2 find_version my $oid = $gridfs->find_version('test.txt', 1); @@ -143,6 +155,15 @@ version. You can also append a callback to perform operation non-blocking. }); Mojo::IOLoop->start unless Mojo::IOLoop->is_running; +=head2 find_version_p + + my $promise = $gridfs->find_version_p($oid); + +Same as L, but performs a non-blocking operation +and returns a L object instead of accepting a callback. + +Notice that promise support depends on L (L 7.53+). + =head2 list my $names = $gridfs->list; @@ -155,6 +176,15 @@ List files. You can also append a callback to perform operation non-blocking. }); Mojo::IOLoop->start unless Mojo::IOLoop->is_running; +=head2 list_p + + my $promise = $gridfs->list_p; + +Same as L, but performs a non-blocking operation +and returns a L object instead of accepting a callback. + +Notice that promise support depends on L (L 7.53+). + =head2 reader my $reader = $gridfs->reader; diff --git a/lib/Mango/GridFS/Reader.pm b/lib/Mango/GridFS/Reader.pm index 4344608..9ffef15 100644 --- a/lib/Mango/GridFS/Reader.pm +++ b/lib/Mango/GridFS/Reader.pm @@ -2,9 +2,12 @@ package Mango::GridFS::Reader; use Mojo::Base -base; use Carp 'croak'; +use Mango::Promises; has 'gridfs'; +Mango::Promises->generate_p_methods(qw(open/0 read slurp)); + sub chunk_size { shift->{meta}{chunkSize} } sub content_type { shift->{meta}{contentType} } sub filename { shift->{meta}{filename} } @@ -179,6 +182,15 @@ Open file. You can also append a callback to perform operation non-blocking. }); Mojo::IOLoop->start unless Mojo::IOLoop->is_running; +=head2 open_p + + my $promise = $reader->open_p; + +Same as L, but performs a non-blocking operation +and returns a L object instead of accepting a callback. + +Notice that promise support depends on L (L 7.53+). + =head2 read my $chunk = $reader->read; @@ -191,6 +203,15 @@ Read chunk. You can also append a callback to perform operation non-blocking. }); Mojo::IOLoop->start unless Mojo::IOLoop->is_running; +=head2 read_p + + my $promise = $reader->read_p; + +Same as L, but performs a non-blocking operation +and returns a L object instead of accepting a callback. + +Notice that promise support depends on L (L 7.53+). + =head2 seek $reader = $reader->seek(13); @@ -216,6 +237,15 @@ operation non-blocking. }); Mojo::IOLoop->start unless Mojo::IOLoop->is_running; +=head2 slurp_p + + my $promise = $slurper->slurp_p; + +Same as L, but performs a non-blocking operation +and returns a L object instead of accepting a callback. + +Notice that promise support depends on L (L 7.53+). + =head2 tell my $pos = $reader->tell; diff --git a/lib/Mango/GridFS/Writer.pm b/lib/Mango/GridFS/Writer.pm index 375db1e..4044c44 100644 --- a/lib/Mango/GridFS/Writer.pm +++ b/lib/Mango/GridFS/Writer.pm @@ -5,10 +5,13 @@ use Carp 'croak'; use List::Util 'first'; use Mango::BSON qw(bson_bin bson_doc bson_oid bson_time); use Mojo::IOLoop; +use Mango::Promises; has chunk_size => 261120; has [qw(content_type filename gridfs metadata)]; +Mango::Promises->generate_p_methods(qw(close write/0)); + sub close { my ($self, $cb) = @_; @@ -187,6 +190,15 @@ Close file. You can also append a callback to perform operation non-blocking. }); Mojo::IOLoop->start unless Mojo::IOLoop->is_running; +=head2 close_p + + my $promise = $writer->close_p; + +Same as L, but performs a non-blocking operation +and returns a L object instead of accepting a callback. + +Notice that promise support depends on L (L 7.53+). + =head2 is_closed my $success = $writer->is_closed; @@ -205,6 +217,15 @@ Write chunk. You can also append a callback to perform operation non-blocking. }); Mojo::IOLoop->start unless Mojo::IOLoop->is_running; +=head2 write_p + + my $promise = $writer->write_p; + +Same as L, but performs a non-blocking operation +and returns a L object instead of accepting a callback. + +Notice that promise support depends on L (L 7.53+). + =head1 SEE ALSO L, L, L. diff --git a/lib/Mango/Promises.pm b/lib/Mango/Promises.pm new file mode 100644 index 0000000..7044206 --- /dev/null +++ b/lib/Mango/Promises.pm @@ -0,0 +1,95 @@ + +package Mango::Promises; + +use Mojo::Base -strict; + +use Carp (); +use Mojo::Util (); + +use constant PROMISES => !!eval { require Mojo::Promise; 1 }; + +sub generate_p_methods { + my ($class, $package) = (shift, scalar caller); + my %patch; + for (@_) { + my ($meth, $arity) = split '/'; + my $args = ($arity // 1) eq '0' ? '' : '$_[2]'; + my $code = q[ + sub { + Carp::croak "METHOD\_p() requires Mojo::Promise" unless PROMISES; + my $self = shift; + my $promise = Mojo::Promise->new; + $self->METHOD( + @_ => sub { + $_[1] ? $promise->reject($_[1]) : $promise->resolve(ARGS); + } + ); + return $promise; + } + ]; + s/\bMETHOD\b/$meth/g, s/\bARGS\b/$args/g for $code; + $patch{"${meth}_p"} = eval $code; + } + Mojo::Util::monkey_patch $package, %patch; +} + +1; + +=encoding utf8 + +=head1 NAME + +Mango::Promises - Mango with promises + +=head1 SYNOPSIS + + use Mango; + use feature 'state'; + + # Declare a Mango helper + sub mango { state $m = Mango->new('mongodb://localhost:27017') } + + # Non-blocking concurrent find + my @u = map { + mango->db('test')->collection('users')->find_one_p({username => $_}) + } qw(sri marty); + Mojo::Promise->all(@u)->then( + sub { + say $_->[0]{display_name} for @_; + } + ); + +=head1 DESCRIPTION + +Since version 1.31, L supports non-blocking with promise-returning +methods. The public interface for this capability are the C<_p> methods +at L and related classes. + +L is an internal class that helps to generate and install +promisified versions of methods into L and related classes. +No user-serviceable parts inside. + +Note that promise support depends on L (L 7.53+). +If L can't be loaded, every C<_p> method will croak +with a message such as + + insert_p() requires Mojo::Promise + +=begin :private + +=head1 METHODS + +=head2 generate_p_methods + + Mango::Promises->generate_p_methods(@methods); + +This method, I, installs into +the caller package promisified versions of the given methods. + +=end :private + +=head1 SEE ALSO + +L, L. + +=cut diff --git a/t/bulk.t b/t/bulk.t index d5ddd25..b22d9fb 100644 --- a/t/bulk.t +++ b/t/bulk.t @@ -46,6 +46,27 @@ is_deeply $result->{upserted}, [], 'no upserts'; is_deeply $result->{writeConcernErrors}, [], 'no write concern errors'; is_deeply $result->{writeErrors}, [], 'no write errors'; +# Non-blocking with promises +if (Mango::PROMISES) { + $collection->bulk->execute_p->then( + sub { + my $result = shift; + is $result->{nInserted}, 0, 'no inserts - p'; + is $result->{nMatched}, 0, 'no matches - p'; + is $result->{nModified}, 0, 'no modifications - p'; + is $result->{nRemoved}, 0, 'no removals - p'; + is $result->{nUpserted}, 0, 'no upserts - p'; + is_deeply $result->{upserted}, [], 'no upserts - p'; + is_deeply $result->{writeConcernErrors}, [], 'no write concern errors - p'; + is_deeply $result->{writeErrors}, [], 'no write errors - p'; + }, + sub { + my $err = shift; + fail("execute_p failed, err: $err"); # should not happen + } + )->wait; +} + # Mixed bulk operations blocking my $bulk = $collection->bulk; ok $bulk->ordered, 'ordered bulk operations'; @@ -89,6 +110,31 @@ ok $result->{upserted}[0], 'one upsert'; is_deeply $result->{writeConcernErrors}, [], 'no write concern errors'; is_deeply $result->{writeErrors}, [], 'no write errors'; +# Mixed bulk operations with promises +if (Mango::PROMISES) { + $bulk = $collection->bulk; + $bulk->insert({foo => 'bar'}); + $bulk->find({foo => 'bar'})->update_one({foo => 'baz'}); + $bulk->find({foo => 'yada'})->upsert->update_one({foo => 'baz'}); + $bulk->find({foo => 'baz'})->remove; + $bulk->execute_p->then( + sub { + my $result = shift; + is $result->{nInserted}, 1, 'one insert - p'; + is $result->{nMatched}, 1, 'one match - p'; + is $result->{nModified}, 2, 'two modifications - p'; + is $result->{nRemoved}, 2, 'two removals - p'; + is $result->{nUpserted}, 1, 'one upsert - p'; + ok $result->{upserted}[0], 'one upsert - p'; + is_deeply $result->{writeConcernErrors}, [], 'no write concern errors - p'; + is_deeply $result->{writeErrors}, [], 'no write errors - p'; + }, + sub { + fail("execute_p failed, err: $_[0]"); # should not happen + } + )->wait; +} + # All operations $bulk = $collection->bulk; $bulk->insert({foo => 'a'})->insert({foo => 'b'})->insert({foo => 'c'}); diff --git a/t/collection.t b/t/collection.t index 9c2aeda..7b960f4 100644 --- a/t/collection.t +++ b/t/collection.t @@ -58,6 +58,19 @@ Mojo::IOLoop->start; ok !$fail, 'no error'; is $result->{count}, 2, 'right number of documents'; +# Get collection statistics with promises +if (Mango::PROMISES) { + $collection->stats_p->then( + sub { + my $result = shift; + is $result->{count}, 2, 'right number of documents - p'; + }, + sub { + fail("stats_p failed, err: $_[0]"); # should not happen + } + )->wait; +} + # Rename the collection ok $collection = $collection->rename('renamed'), 'collection renamed'; $collection->rename('collection_test' => sub { @@ -71,6 +84,28 @@ ok !$fail, 'no error'; is $result->name, 'collection_test', 'collection renamed non-blocking'; $collection = $result; +# Rename the collection with promises +if (Mango::PROMISES) { + $collection->rename_p('renamed')->then( + sub { + my $c = shift; + is $c->name, 'renamed', 'collection renamed - p'; + $c->rename_p('collection_test')->then( + sub { + my $c = shift; + is $c->name, 'collection_test', 'collection name restored - p'; + }, + sub { + fail("2nd rename_p failed, err: $_[0]"); # should not happen + } + ); + }, + sub { + fail("rename_p failed, err: $_[0]"); # should not happen + } + )->wait; +} + # Update documents blocking is $collection->update({}, {'$set' => {bar => 'works'}}, {multi => 1})->{n}, 2, 'two documents updated'; @@ -330,6 +365,20 @@ Mojo::IOLoop->start; ok !$fail, 'no error'; ok !$collection->find_one($oid), 'no document'; +# Drop collection with promises +if (Mango::PROMISES) { + $oid = $collection->insert({just => 'works'}); + is $collection->find_one($oid)->{just}, 'works', 'right document'; + $collection->drop_p->then( + sub { + ok !$collection->find_one($oid), 'no document'; + }, + sub { + fail("drop_p failed, err: $_[0]"); # should not happen + } + )->wait; +} + # Ensure and drop index blocking $collection->insert({test => 23, foo => 'bar'}); $collection->ensure_index({test => 1}, {unique => \1}); diff --git a/t/cursor.t b/t/cursor.t index 0795d16..1ecedbb 100644 --- a/t/cursor.t +++ b/t/cursor.t @@ -173,6 +173,19 @@ Mojo::IOLoop->start; ok !$fail, 'no error'; is_deeply [sort @$result], [2, 3], 'right values'; +# Get distinct values with promises +if (Mango::PROMISES) { + $collection->find({test => {'$gt' => 1}})->distinct_p('test')->then( + sub { + my $values = shift; + is_deeply [sort @$values], [2, 3], 'right values - p'; + }, + sub { + fail("distinct_p failed, err: $_[0]"); # should not happen + } + )->wait; +} + # Count documents blocking is $collection->find({foo => 'bar'})->count, 0, 'no documents'; is $collection->find->skip(1)->limit(1)->count, 1, 'one document'; @@ -202,6 +215,29 @@ $delay->wait; ok !$fail, 'no error'; is_deeply \@results, [3, 0], 'right number of documents'; +# Count documents with promises +if (Mango::PROMISES) { + $collection->find->count_p->then( + sub { + my $count = shift; + is $count, 3, 'right collection count - p'; + + $collection->find({foo => 'bar'})->count_p; + }, + sub { + fail("count_p failed, err: $_[0]"); # should not happen + } + )->then( + sub { + my $count = shift; + is $count, 0, 'right number of documents - p'; + }, + sub { + fail("count_p failed, err: $_[0]"); # should not happen + } + ); +} + # Fetch documents non-blocking $cursor = $collection->find->batch_size(2); @docs = (); @@ -236,6 +272,38 @@ is $docs[0]{test}, 1, 'right document'; is $docs[1]{test}, 2, 'right document'; is $docs[2]{test}, 3, 'right document'; +# Fetch documents with promises +if (Mango::PROMISES) { + $cursor = $collection->find->batch_size(2); + @docs = (); + $cursor->next_p->then( + sub { + push @docs, shift; + $cursor->next_p; + } + )->then( + sub { + push @docs, shift; + $cursor->next_p; + } + )->then( + sub { + push @docs, shift; + $cursor->next_p; + }, + sub { + fail("next_p failed, err: $_[0]"); # should not happen + } + )->then( + sub { + @docs = sort { $a->{test} <=> $b->{test} } @docs; + is $docs[0]{test}, 1, 'right document - p'; + is $docs[1]{test}, 2, 'right document - p'; + is $docs[2]{test}, 3, 'right document - p'; + } + )->wait; +} + # Fetch all documents non-blocking @docs = (); $collection->find->batch_size(2)->all( @@ -251,6 +319,22 @@ is $docs[0]{test}, 1, 'right document'; is $docs[1]{test}, 2, 'right document'; is $docs[2]{test}, 3, 'right document'; +# Fetch all documents with promises +if (Mango::PROMISES) { + $collection->find->batch_size(2)->all_p->then( + sub { + my $docs = shift; + my @docs = sort { $a->{test} <=> $b->{test} } @$docs; + is $docs[0]{test}, 1, 'right document - p'; + is $docs[1]{test}, 2, 'right document - p'; + is $docs[2]{test}, 3, 'right document - p'; + }, + sub { + fail("all_p failed, err: $_[0]"); # should not happen + } + )->wait; +} + # Fetch subset of documents sorted $docs = $collection->find->fields({_id => 0})->sort({test => 1})->all; is_deeply $docs, [{test => 1}, {test => 2}, {test => 3}], 'right subset'; diff --git a/t/database.t b/t/database.t index 198f310..7a4f22e 100644 --- a/t/database.t +++ b/t/database.t @@ -28,6 +28,19 @@ Mojo::IOLoop->start; ok !$fail, 'no error'; ok $result, 'command was successful'; +# Run command with promises +if (Mango::PROMISES) { + $db->command_p('getnonce')->then( + sub { + my $doc = shift; + ok $doc->{nonce}, 'command_p was successful'; + }, + sub { + fail("command_p failed, err: $_[0]"); # should not happen + } + )->wait; +} + # Write concern my $mango2 = Mango->new->w(2)->wtimeout(5000); my $concern = $mango2->db('test')->build_write_concern; @@ -51,6 +64,19 @@ Mojo::IOLoop->start; ok !$fail, 'no error'; ok exists $result->{objects}, 'has objects'; +# Get database statistics with promises +if (Mango::PROMISES) { + $db->stats_p->then( + sub { + my $stats = shift; + ok exists $stats->{objects}, 'stats_p: has objects'; + }, + sub { + fail("stats_p failed, err: $_[0]"); # should not happen + } + )->wait; +} + # List collections my $collection = $db->collection('database_test'); $collection->insert({test => 1}); @@ -79,6 +105,21 @@ ok !$fail, 'no error'; ok grep { $_ eq 'database_test' } @$result, 'found collection'; $collection->drop; +# Get collection names with promises +if (Mango::PROMISES) { + $collection->insert({test => 1}); + $db->collection_names_p->then( + sub { + my $names = shift; + ok grep { $_ eq 'database_test' } @$names, 'collection_names_p: found collection'; + }, + sub { + fail("collection_names_p failed, err: $_[0]"); # should not happen + } + ); + $collection->drop; +} + # Dereference blocking my $oid = $collection->insert({test => 23}); is $db->dereference(bson_dbref('database_test', $oid))->{test}, 23, @@ -101,6 +142,21 @@ ok !$fail, 'no error'; is $result->{test}, 23, 'right result'; $collection->drop; +# Dereference with promises +if (Mango::PROMISES) { + $oid = $collection->insert({test => 23}); + $db->dereference_p(bson_dbref('database_test', $oid))->then( + sub { + my $doc = shift; + is $doc->{test}, 23, 'dereference_p: right result'; + }, + sub { + fail("dereference_p failed, err: $_[0]"); # should not happen + } + )->wait; + $collection->drop; +} + # Interrupted blocking command my $loop = $mango->ioloop; my $id = $loop->server((address => '127.0.0.1') => sub { $_[1]->close }); diff --git a/t/gridfs.t b/t/gridfs.t index 086abe1..6b6be71 100644 --- a/t/gridfs.t +++ b/t/gridfs.t @@ -143,6 +143,121 @@ is_deeply $after, [], 'no files'; is $gridfs->chunks->find->count, 0, 'no chunks left'; $gridfs->$_->drop for qw(files chunks); +# Non-blocking roundtrip with promises +if (Mango::PROMISES) { + + $writer = $gridfs->writer->chunk_size(4); + $writer->filename('foo.txt')->content_type('text/plain') + ->metadata({foo => 'bar'}); + ok !$writer->is_closed, 'file has not been closed'; + + $writer->write_p('he')->then( + sub { + $writer->write_p('llo '); + } + )->then( + sub { + $writer->write_p('w'); + } + )->then( + sub { + $writer->write_p('orld!'); + } + )->then( + sub { + $writer->close_p; + }, + sub { + fail("write_p failed, err: $_[0]"); # should not happen + } + )->then( + sub { + my $oid = shift; + ok $writer->is_closed, 'file has been closed - p'; + $result = $oid; + }, + sub { + fail("close_p failed, err: $_[0]"); # should not happen + } + )->wait; + + $reader = $gridfs->reader; + $reader->open_p($result)->then( + sub { + is $reader->filename, 'foo.txt', 'right filename - p'; + is $reader->content_type, 'text/plain', 'right content type - p'; + is $reader->md5, 'fc3ff98e8c6a0d3087d515c0473f8677', 'right checksum - p'; + is_deeply $reader->metadata, {foo => 'bar'}, 'right structure - p'; + is $reader->size, 12, 'right size - p'; + is $reader->chunk_size, 4, 'right chunk size - p'; + is length $reader->upload_date, length(time) + 3, 'right time format - p'; + }, + sub { + fail("open_p failed, err: $_[0]"); # should not happen + } + )->then( + sub { + my ($data, $cb); + $reader->read_p()->then( + $cb = sub { + my $chunk = shift; + return unless defined $chunk; + $data .= $chunk; + $reader->read_p()->then($cb); + } + )->then( + sub { + is $data, 'hello world!', 'right content - p'; + }, + sub { + fail("read_p failed, err: $_[0]"); # should not happen + } + ); + } + )->then( + sub { + $reader->seek(0); # rewind + $reader->slurp_p(); + } + )->then( + sub { + my $data = shift; + is $data, 'hello world!', 'right slurped content - p'; + }, + sub { + fail("slurp_p failed, err: $_[0]"); # should not happen + } + )->wait; + + $gridfs->list_p()->then( + sub { + my $names = shift; + is_deeply $names, ['foo.txt'], 'right files - p'; + + $gridfs->delete_p($result); + }, + sub { + fail("list_p failed, err: $_[0]"); # should not happen + } + )->then( + sub { + $gridfs->list_p; + }, + sub { + fail("delete_p failed, err: $_[0]"); # should not happen + } + )->then( + sub { + my $names = shift; + is_deeply $names, [], 'no files - p'; + + is $gridfs->chunks->find->count, 0, 'no chunks left - p'; + } + )->wait; + + $gridfs->$_->drop for qw(files chunks); +} + # Find and slurp versions blocking my $one = $gridfs->writer->chunk_size(1)->filename('test.txt')->write('One1')->close;