=pod

=encoding UTF-8

=head1 NAME

Riak::Client - Fast and lightweight Perl client for Riak

=head1 VERSION

version 1.91

=head1 SYNOPSIS

  use Riak::Client;

  # normal mode
  my $client = Riak::Client->new(
    host => '127.0.0.1',
    port => 8087,
    r => 2,
    w => 2,
    dw => 1,
    connection_timeout => 5,
    read_timeout => 5,
    write_timeout => 5,
    no_auto_connect => 0,
  );

  # AnyEvent mode
  my $client = Riak::Client->new(
    host => '127.0.0.1',
    port => 8087
    anyevent_mode => 1,
  );

  $client->is_alive() or die "riak is not alive";

  # store hashref. will be serialized as JSON
  $client->put( 'bucket_name', 'key_name', { some => 'structure' } );

  # store text
  $client->put( 'bucket_name', 'key_name', 'sometext', 'text/plain' );

  # store raw data
  $client->put_raw( 'bucket_name', 'key_name', 'rawdata' );

  # fetch hashref
  my $hash = $client->get( 'bucket_name', 'key_name' );

  # fetch raw data
  my $text = $client->get_raw( 'bucket_name', 'key_name');

  # delete data
  $client->del( 'bucket_name', 'key_name');

  # AnyEvent mode
  my $cv = AE::cv
  $client->get_raw( 'bucket_name', 'key_name'
                    sub { do_something_with($_[0]);
                          $cv->send();
                    }
              );
  # ... later one
  $cv->recv();

  # list keys in stream
  $client->get_keys(foo => sub{
     my ($key, $done) = @_;

     # you should use another client inside this callback!
     $another_client->del(foo => $key);

  });

=head1 DESCRIPTION

Riak::Client is a fast and light Perl client for Riak using PBC interface, with
optional AnyEvent mode.

It supports operations like ping, get, exists, put, del, and secondary indexes
(so-called 2i) setting and querying.

It has two modes, a traditional procedural mode, and an event based mode, using
AnyEvent.

It started as a fork of C<Riak::Light> to fix some bugs, but actually ended up
in a complete rewrite with more features, but the same performance.

=head1 ATTRIBUTES

=head2 anyevent_mode

Enables the AnyEvent mode, allowing true asynchronous mode.

=head2 host

Str, Required. Riak IP or hostname.

=head2 port

Int, Required. Port of the PBC interface.

=head2 r

Int, Default 2. R value setting for this client.

=head2 w

Int, Default 2. W value setting for this client.

=head2 dw

Int, Default 1. DW value setting for this client.

=head2 connection_timeout

Float, Default 5. Timeout for connection operation, in seconds. Set to 0 for no timeout.

=head2 read_timeout

Float, Default 5. Timeout for read operation, in seconds. Set to 0 for no timeout.

=head2 no_delay

Boolean, Default 0. If set to a true value, TCP_NODELAY will be enabled on the
socket, which means deactivating Nagle's algorithm. Use only if you know what
you're doing.

=head2 no_auto_connect

Bool, Default 0. If set to true, then the module won't automatically connect upon instanciation.
Instead, you'll have to call C<connect()> yourself.

=head2 anyevent_mode

Bool, Default 0. If set to true, then all methods can receive a callback,
as last argument. If present, the method will return immediately, and the
callback will be executed upon completion of the operation, receiving a condvar
as first and only argument. If set to false (the default), then the client
instance will be synchronous.

=head1 METHODS

=head2 connect

  my $client->connect();

  # or in AnyEvent mode
  $cv = AE::cv;
  $client->connect(sub { print "connected!\n"; $cv->send(); });
  $cv->recv();

Connects to the Riak server. This is automatically done when C<new()> is
called, unless the C<no_auto_connect> attribute is set to true. In AnyEvent
mode, returns a conditional variable,that will be triggered when connected.

=head2 ping

  use Try::Tiny;
  try { $client->ping() } catch { "oops... something is wrong: $_" };

Perform a ping operation. Will die in case of error. See C<is_alive()>

=head2 is_alive

  $client->is_alive() or warn "oops... something is wrong: $@";

Perform a ping operation. Will return false in case of error (which will be stored in $@).

=head2 get

  # blocking mode
  my $value = $client->get(bucket => 'key');

  # blocking mode, with a callback
  $client->get(bucket => 'key', sub { my ($value) = @_; do_stuff($value) });

  # AnyEvent mode, asynchronous
  $cv = AE::cv;
  $client->get(bucket => 'key', sub { my ($value) = @_; do_stuff($value); $cv->send() });
  # ...
  $cv->recv();

Perform a fetch operation. Expects bucket and key names. If the content_type of
the fetched value is 'application/json', automatically decodes the JSON into a
Perl structure. If you need the raw data you can use C<get_raw>.

=head2 get_raw

  my $scalar_value = $client->get_raw(bucket => 'key');

Perform a fetch operation. Expects bucket and key names. Returns the raw data.
If you want json to be automatically decoded, you should use C<get()> instead.

=head2 put

  $client->put('bucket', 'key', { some_values => [1,2,3] });
  $client->put('bucket', 'key', { some_values => [1,2,3] }, 'application/json);
  $client->put('bucket', 'key', 'text', 'plain/text');

  # you can set secondary indexes (2i)
  $client->put( 'bucket', 'key', 'text_value', 'plain/text',
                { field1_bin => 'abc', field2_int => 42 }
              );
  $client->put( 'bucket', 'key', { some_values => [1,2,3] }, undef,
                { field1_bin => 'abc', field2_int => 42 }
              );

  # you can also set links
  $client->put( 'bucket', 'key', 'text', 'plain/text', undef,
                { link_tag1 => 'bucket/key',
                  link_tag2 => 'other_bucket/key',
                }
              );

  # you can set multiple links for the same tag
  $client->put( 'bucket', 'key', 'text', 'plain/text', undef,
                { link_tag1 => [ qw( bucket/key bucket2/key2 ) ],
                  link_tag2 => 'other_bucket/key',
                }
              );

  # you can also use this form (marginally faster)
  $client->put( 'bucket', 'key', 'text', 'plain/text', undef,
                [ { tag => 'link_tag1', bucket => 'bucket1', key => 'key1'},
                  { tag => 'link_tag2', bucket => 'bucket2', key => 'key2'},
                ],
              );

Perform a store operation. Expects bucket and key names, the value, the content
type (optional, default is 'application/json'), and the indexes to set for this
value (optional, default is none).

Will encode the structure in json string if necessary. If you need only store
the raw data you can use C<put_raw> instead.

B<IMPORTANT>: all the index field names should end by either C<_int> or
C<_bin>, depending if the index type is integer or binary.

To query secondary indexes, see C<query_index>.

=head2 put_raw

  $client->put_raw('bucket', 'key', encode_json({ some_values => [1,2,3] }), 'application/json');
  $client->put_raw('bucket', 'key', 'text');
  $client->put_raw('bucket', 'key', 'text', undef, {field_bin => 'foo'});
  $client->put_raw('bucket', 'key', 'text', undef, {field_bin => 'foo'}, $links);

For more example, see C<put>.

Perform a store operation. Expects bucket and key names, the value, the content
type (optional, default is 'plain/text'), the indexes (optional, default is
none), and links (optional, default is none) to set for this value

This method won't encode the data, but pass it as such, trusting it's in the
type you've indicated in the passed content-type. If you want the structure to
be automatically encoded, use C<put> instead.

B<IMPORTANT>: all the index field names should end by either C<_int> or
C<_bin>, depending if the index type is integer or binary.

To query secondary indexes, see C<query_index>.

=head2 del

  $client->del(bucket => key);

Perform a delete operation. Expects bucket and key names.

=head2 get_keys

  # in default mode
  $client->get_keys(foo => sub{
     my ($key, $done) = @_;
     # you should use another client inside this callback!
     $another_client->del(foo => $key);
  });

  # in anyevent mode
  my $cv = AE::cv;
  $client->get_keys(foo => sub{
     my ($key, $done) = @_;
     # ... do stuff with $key
     $done and $cv->send;
  });
  $cv->recv();

B<WARNING>, this method should not be called on a production Riak cluster, as
it can have a big performance impact. See Riak's documentation.

B<WARNING>, because Riak doesn't handles pipelining, you cannot use the same
C<Riak::Client> instance inside the callback, it would raise an exception.

Perform a list keys operation. Receive a callback and will call it for each
key. The callback will receive two arguments: the key, and a boolean indicating
if it's the last key

The callback is optional, in which case an ArrayRef of B<all> the keys are
returned. But don't do that, and always provide a callback, to avoid your RAM
usage to skyrocket...

=head2 exists

  $client->exists(bucket => 'key') or warn "key not found";

Perform a fetch operation but with head => 0, and the if there is something
stored in the bucket/key.

=head2 query_index

Perform a secondary index (2i) query. Expects a bucket name, the index field
name, the index value you're searching on, and optionally a callback.

If a callback has been provided, doesn't return anything, but execute the
callback on each matching keys. callback will receive the key name as first
argument. key name will also be in C<$_>. If no callback is provided, returns
and ArrayRef of matching keys.

The index value you're searching on can be of two types. If it's a Scalar, an
B<exact match> query will be performed. if the value is an ArrayRef, then a
B<range> query will be performed, the first element in the array will be the
range_min, the second element the range_max. other elements will be ignored.

Based on the example in C<put>, here is how to query it:

  # exact match
  my $matching_keys = $client->query_index( 'bucket',  'field2_int', 42 ),

  # range match
  my $matching_keys = $client->query_index( 'bucket',  'field2_int', [ 40, 50] ),

  # range match with callback
  $client->query_index( 'bucket',  'field2_int', [ 40, 50], sub { print "key : $_" } ),

=head2 get_buckets

B<WARNING>, this method should not be called on a production Riak cluster, as
it can have a big performance impact. See Riak's documentation.

=head2 get_bucket_props

=head2 set_bucket_props

=head2 map_reduce

=head2 map_reduce_raw

=head1 BENCHMARKS

Note: These benchmarks are the one provided by C<Riak::Light>.

Note: the AnyEvent mode is a bit slower below, because we are forcing
synchronous mode, even in AnyEvent, so the benchmark is paying the price of
having AnyEvent enabled but not used.

=head2 GETS

                                  Rate Data::Riak (REST) Riak::Tiny (REST) Net::Riak (REST) Data::Riak::Fast (REST) Net::Riak (PBC) Riak::Client (PBC + AnyEvent) Riak::Light (PBC) Riak::Client (PBC)
  Data::Riak (REST)              427/s                --              -30%             -31%                    -43%            -65%                          -85%              -90%               -91%
  Riak::Tiny (REST)              611/s               43%                --              -2%                    -19%            -51%                          -79%              -86%               -87%
  Net::Riak (REST)               623/s               46%                2%               --                    -17%            -50%                          -78%              -86%               -87%
  Data::Riak::Fast (REST)        755/s               77%               24%              21%                      --            -39%                          -74%              -83%               -84%
  Net::Riak (PBC)               1238/s              190%              103%              99%                     64%              --                          -57%              -72%               -74%
  Riak::Client (PBC + AnyEvent) 2878/s              573%              371%             362%                    281%            132%                            --              -34%               -39%
  Riak::Light (PBC)             4348/s              917%              612%             598%                    476%            251%                           51%                --                -8%
  Riak::Client (PBC)            4706/s             1001%              671%             655%                    524%            280%                           64%                8%                 --

=head2 PUTS

                                  Rate Net::Riak (REST) Data::Riak (REST) Riak::Tiny (REST) Data::Riak::Fast (REST) Net::Riak (PBC) Riak::Light (PBC) Riak::Client (PBC + AnyEvent) Riak::Client (PBC)
  Net::Riak (REST)               542/s               --              -15%              -29%                    -55%            -57%              -90%                          -92%               -92%
  Data::Riak (REST)              635/s              17%                --              -17%                    -47%            -49%              -89%                          -90%               -90%
  Riak::Tiny (REST)              765/s              41%               20%                --                    -36%            -39%              -86%                          -88%               -88%
  Data::Riak::Fast (REST)       1198/s             121%               89%               57%                      --             -4%              -79%                          -82%               -82%
  Net::Riak (PBC)               1254/s             131%               97%               64%                      5%              --              -78%                          -81%               -81%
  Riak::Light (PBC)             5634/s             939%              787%              637%                    370%            349%                --                          -14%               -14%
  Riak::Client (PBC + AnyEvent) 6557/s            1110%              933%              757%                    448%            423%               16%                            --                 0%
  Riak::Client (PBC)            6557/s            1110%              933%              757%                    448%            423%               16%                            0%                 --

=for Pod::Coverage BUILD

=head1 SEE ALSO

L<Net::Riak>

L<Data::Riak>

L<Data::Riak::Fast>

L<Action::Retry>

L<Riak::Light>

L<AnyEvent>

=head1 AUTHOR

Damien Krotkine <dams@cpan.org>

=head1 COPYRIGHT AND LICENSE

This software is copyright (c) 2014 by Damien Krotkine.

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

=cut
