Code Monkey home page Code Monkey logo

riak-erlang-client's Introduction

Riak Erlang Client

This document assumes that you have already started your Riak cluster. For instructions on that prerequisite, refer to the setup guide in the Basho Docs. You can also view the Riak Erlang Client EDocs here.

Build Status

Build Status

Dependencies

To build the riak-erlang-client you will need Erlang OTP 20.3 or later, and Git.

Debian

On a Debian based system (Debian, Ubuntu, ...) you will need to make sure that certain packages are installed:

# apt-get install erlang-parsetools erlang-dev erlang-syntax-tools

Installing

    $ git clone git://github.com/basho/riak-erlang-client.git
    $ cd riak-erlang-client
    $ make

Connecting

To talk to riak, all you need is an Erlang node with the riak-erlang-client library (riakc) in its code path.

    $ erl -pa $PATH_TO_RIAKC/ebin $PATH_TO_RIAKC/deps/*/ebin

You'll know you've done this correctly if you can execute the following commands and get a path to a beam file, instead of the atom 'non_existing':

   1> code:which(riakc_pb_socket).
   ".../riak-erlang-client/ebin/riakc_pb_socket.beam"

Once you have your node running, pass your Riak server nodename to riakc_pb_socket:start_link/2 to connect and get a client. This can be as simple as:

   1> {ok, Pid} = riakc_pb_socket:start_link("127.0.0.1", 8087).
   {ok,<0.56.0>}

Verify connectivity with the server using ping/1.

   2> riakc_pb_socket:ping(Pid).
   pong

Storing New Data

Each bit of data in Riak is stored in a "bucket" at a "key" that is unique to that bucket. The bucket is intended as an organizational aid, for example to help segregate data by type, but Riak doesn't care what values it stores, so choose whatever scheme suits you. Buckets, keys and values are all binaries.

Before storing your data, you must wrap it in a riakc_obj:

3> Object = riakc_obj:new(<<"groceries">>, <<"mine">>, <<"eggs & bacon">>).
{riakc_obj,<<"groceries">>,<<"mine">>,undefined,undefined,
{dict,0,16,16,8,80,48,
{[],[],[],[],[],[],[],[],[],[],[],[],[],[],[],...},
{{[],[],[],[],[],[],[],[],[],[],[],[],[],...}}},
<<"eggs & bacon">>}

If you want to have the server generate you a key (similar to the REST API) pass the atom undefined as the second parameter to new().

The Object refers to a key <<"mine">> in a bucket named <<"groceries">> with the value <<"eggs & bacon">>. Using the client you opened earlier, store the object:

5> riakc_pb_socket:put(Pid, Object).
ok

If the return value of the last command was anything but the atom ok (or {ok, Key} when you instruct the server to generate the key), then the store failed. The return value may give you a clue as to why the store failed, but check the Troubleshooting section below if not.

The object is now stored in Riak. put/2 uses default parameters for storing the object. There is also a put/3 call that takes a proplist of options.

Option Description
{w, W} the minimum number of nodes that must respond with success for the write to be considered successful. The default is currently set on the server to quorum.
{dw, DW} the minimum number of nodes that must respond with success * *after durably storing* the object for the write to be considered successful. The default is currently set on the server to quorum.
return_body immediately do a get after the put and return a riakc_obj.

See Default Bucket Properties for more details.

6> AnotherObject = riakc_obj:new(<<"my bucket">>, <<"my key">>, <<"my binary data">>).
7> riakc_pb_socket:put(Pid, AnotherObject, [{w, 2}, {dw, 1}, return_body]).
{ok,{riakc_obj,<<"my bucket">>,<<"my key">>,
<<107,206,97,96,96,96,206,96,202,5,82,44,140,62,169,115,
50,152,18,25,243,88,25,...>>,
[{{dict,2,16,16,8,80,48,
{[],[],[],[],[],[],[],[],[],[],[],[],...},
{{[],[],[],[],[],[],[],[],[],[],...}}},
<<"my binary data">>}],
{dict,0,16,16,8,80,48,
{[],[],[],[],[],[],[],[],[],[],[],[],[],...},
{{[],[],[],[],[],[],[],[],[],[],[],...}}},
undefined}}

Would make sure at least two nodes responded successfully to the put and at least one node has durably stored the value and an updated object is returned.

See this page for more information about W and DW values.

Fetching Data

At some point you'll want that data back. Using the same bucket and key you used before:

8> {ok, O} = riakc_pb_socket:get(Pid, <<"groceries">>, <<"mine">>).
{ok,{riakc_obj,<<"groceries">>,<<"mine">>,
<<107,206,97,96,96,96,204,96,202,5,82,44,12,143,167,115,
103,48,37,50,230,177,50,...>>,
[{{dict,2,16,16,8,80,48,
{[],[],[],[],[],[],[],[],[],[],[],[],...},
{{[],[],[],[],[],[],[],[],[],[],...}}},
<<"eggs & bacon">>}],
{dict,0,16,16,8,80,48,
{[],[],[],[],[],[],[],[],[],[],[],[],[],...},
{{[],[],[],[],[],[],[],[],[],[],[],...}}},
undefined}}

Like put/3, there is a get/4 function that takes options.

Option Description
{r, R} the minimum number of nodes that must respond with success for the read to be considered successful

If the data was originally stored using the distributed erlang client (riak_client), the server will automatically term_to_binary/1 the value before sending it, with the content type set to application/x-erlang-binary (replacing any user-set value). The application is responsible for calling binary_to_term to access the content and calling term_to_binary when modifying it.

Modifying Data

Say you had the "grocery list" from the examples above, reminding you to get <<"eggs & bacon">>, and you want to add <<"milk">> to it. The easiest way is:

9> {ok, Oa} = riakc_pb_socket:get(Pid, <<"groceries">>, <<"mine">>).
...
10> Ob = riakc_obj:update_value(Oa, <<"milk, ", (riakc_obj:get_value(Oa))/binary>>).
11> {ok, Oc} = riakc_pb_socket:put(Pid, Ob, [return_body]).
{ok,{riakc_obj,<<"groceries">>,<<"mine">>,
<<107,206,97,96,96,96,206,96,202,5,82,44,12,143,167,115,
103,48,37,50,230,177,50,...>>,
[{{dict,2,16,16,8,80,48,
{[],[],[],[],[],[],[],[],[],[],[],[],...},
{{[],[],[],[],[],[],[],[],[],[],...}}},
<<"milk, eggs & bacon">>}],
{dict,0,16,16,8,80,48,
{[],[],[],[],[],[],[],[],[],[],[],[],[],...},
{{[],[],[],[],[],[],[],[],[],[],[],...}}},
undefined}}

That is, fetch the object from Riak, modify its value with riakc_obj:update_value/2, then store the modified object back in Riak. You can get your updated object to convince yourself that your list is updated:

Deleting Data

Throwing away data is quick and simple: just use the delete/3 function.

10> riakc_pb_socket:delete(Pid, <<"groceries">>, <<"mine">>).
ok

As with get and put, delete can also take options

Option Description
{rw, RW} the number of nodes to wait for responses from

Issuing a delete for an object that does not exist returns just returns ok.

Encoding

The initial release of the erlang protocol buffers client treats all values as binaries. The caller needs to make sure data is serialized and deserialized correctly. The content type stored along with the object may be used to store the encoding. For example

decode_term(Object) ->
  case riakc_obj:get_content_type(Object) of
    <<"application/x-erlang-term">> ->
      try
        {ok, binary_to_term(riakc_obj:get_value(Object))}
      catch
        _:Reason ->
          {error, Reason}
      end;
    Ctype ->
      {error, {unknown_ctype, Ctype}}
  end.

encode_term(Object, Term) ->
  riakc_obj:update_value(Object, term_to_binary(Term, [compressed]),
  <<"application/x-erlang-term">>).

Siblings

If a bucket is configured to allow conflicts (allow_mult=true) then the result object may contain more than one result. The number of values can be returned with

1> riakc_obj:value_count(Obj).
2

The values can be listed with

2> riakc_obj:get_values(Obj).
\[<<"{\"k1\":\"v1\"}">>,<<"{\"k1\":\"v2\"}">>\]

And the content types as

3> riakc_obj:get_content_types(Obj).
[]

If resolution simply requires one of the existing siblings to be selected, this can be done through the riakc_obj:select_sibling function. This function updates the record with the value and metadata of the selected Nth sibling.

It is also possible to get a list of tuples representing all the siblings through the riakc_obj:get_contents function. This returns a list of tuples in the form {metadata(), value()} which can be used when more complex sibling resolution is required.

Once the correct combination of metadata and value has been determined, the record can be updated with these using the riakc_obj:update_value and riakc_obj:update_metadata functions. If the resulting content type needs to be updated, the riakc_obj:update_content_type can be used.

Listing Keys

Most uses of key-value stores are structured in such a way that requests know which keys they want in a bucket. Sometimes, though, it's necessary to find out what keys are available (when debugging, for example). For that, there is list_keys:

1> riakc_pb_socket:list_keys(Pid, <<"groceries">>).
{ok,[<<"mine">>]}

Note that keylist updates are asynchronous to the object storage primitives, and may not be updated immediately after a put or delete. This function is primarily intended as a debugging aid.

list_keys/2 is just a convenience function around the streaming version of the call stream_list_keys(Pid, Bucket).

2> riakc_pb_socket:stream_list_keys(Pid, <<"groceries">>).
{ok,87009603}
3> receive Msg1 \-> Msg1 end.
{87009603,{keys,[]}}
4> receive Msg2 \-> Msg2 end.
{87009603,done}

See riakc_utils:wait_for_list for a function to receive data.

Bucket Properties

Bucket properties can be retrieved and modified using get_bucket/2 and set_bucket/3. The bucket properties are represented as a proplist. Only a subset of the properties can be retrieved and set using the protocol buffers interface - currently only n_val and allow_mult.

Here's an example of getting/setting properties

3> riakc_pb_socket:get_bucket(Pid, <<"groceries">>).
{ok,[{n_val,3},{allow_mult,false}]}
4> riakc_pb_socket:set_bucket(Pid, <<"groceries">>, [{n_val, 5}]).
ok
5> riakc_pb_socket:get_bucket(Pid, <<"groceries">>).
{ok,[{n_val,5},{allow_mult,false}]}
6> riakc_pb_socket:set_bucket(Pid, <<"groceries">>, [{n_val, 7}, {allow_mult, true}]).
ok
7> riakc_pb_socket:get_bucket(Pid, <<"groceries">>).
{ok,[{n_val,7},{allow_mult,true}]}

User Metadata

User metadata are stored in the object metadata dictionary, and can be manipulated by using the get_user_metadata_entry/2, get_user_metadata_entries/1, clear_user_metadata_entries/1, delete_user_metadata_entry/2 and set_user_metadata_entry/2 functions.

These functions act upon the dictionary returned by the get_metadata/1, get_metadatas/1 and get_update_metadata/1 functions.

The following example illustrates setting and getting metadata.

%% Create new object
13> Object = riakc_obj:new(<<"test">>, <<"usermeta">>, <<"data">>).
{riakc_obj,<<"test">>,<<"usermeta">>,undefined,[],undefined,
       <<"data">>}
14> MD1 = riakc_obj:get_update_metadata(Object).
{dict,0,16,16,8,80,48,
  {[],[],[],[],[],[],[],[],[],[],[],[],[],[],[],[]},
  {{[],[],[],[],[],[],[],[],[],[],[],[],[],[],[],[]}}}
15> riakc_obj:get_user_metadata_entries(MD1).
[]
16> MD2 = riakc_obj:set_user_metadata_entry(MD1,{<<"Key1">>,<<"Value1">>}).
{dict,1,16,16,8,80,48,
  {[],[],[],[],[],[],[],[],[],[],[],[],[],[],[],[]},
  {{[],[],[],[],[],[],[],[],[],[],[],[],[],[],[],
    [[<<"X-Ri"...>>,{...}]]}}}
17> MD3 = riakc_obj:set_user_metadata_entry(MD2,{<<"Key2">>,<<"Value2">>}).
{dict,1,16,16,8,80,48,
  {[],[],[],[],[],[],[],[],[],[],[],[],[],[],[],[]},
  {{[],[],[],[],[],[],[],[],[],[],[],[],[],[],[],
    [[<<"X-Ri"...>>,{...}|...]]}}}
18> riakc_obj:get_user_metadata_entry(MD3, <<"Key1">>).
<<"Value1">>
19> MD4 = riakc_obj:delete_user_metadata_entry(MD3, <<"Key1">>).
{dict,1,16,16,8,80,48,
  {[],[],[],[],[],[],[],[],[],[],[],[],[],[],[],[]},
  {{[],[],[],[],[],[],[],[],[],[],[],[],[],[],[],
    [[<<"X-Ri"...>>,{...}]]}}}
20> riakc_obj:get_user_metadata_entries(MD4).
[{<<"Key2">>,<<"Value2">>}]
%% Store updated metadata back to the object
21> Object2 = riakc_obj:update_metadata(Object,MD4).
{riakc_obj,<<"test">>,<<"usermeta">>,undefined,[],
       {dict,1,16,16,8,80,48,
             {[],[],[],[],[],[],[],[],[],[],[],[],[],[],[],...},
             {{[],[],[],[],[],[],[],[],[],[],[],[],[],...}}},
       <<"data">>}
22> riakc_pb_socket:put(Pid, Object2).
ok
23> {ok, O1} = riakc_pb_socket:get(Pid, <<"test">>, <<"usermeta">>).
{ok,{riakc_obj,<<"test">>,<<"usermeta">>,
           <<107,206,97,96,96,96,204,96,202,5,82,28,202,156,255,126,
             6,220,157,173,153,193,148,...>>,
           [{{dict,3,16,16,8,80,48,
                   {[],[],[],[],[],[],[],[],[],[],[],[],...},
                   {{[],[],[],[],[],[],[],[],[],[],...}}},
             <<"data">>}],
           undefined,undefined}}
24> riakc_obj:get_user_metadata_entries(riakc_obj:get_update_metadata(O1)).
[{<<"Key2">>,<<"Value2">>}]

Secondary Indexes

Secondary indexes are set through the object metadata dictionary, and can be manipulated by using the get_secondary_index/2, get_secondary_indexes/1, clear_secondary_indexes/1, delete_secondary_index/2, set_secondary_index/2 and add_secondary_index/2 functions. These functions act upon the dictionary returned by the get_metadata/1, get_metadatas/1 and get_update_metadata/1 functions.

When using these functions, secondary indexes are identified by a tuple, {binary_index, string()} or {integer_index, string()}, where the string is the name of the index. {integer_index, "id"} therefore corresponds to the index "id_int". As secondary indexes may have more than one value, the index values are specified as lists of integers or binaries, depending on index type.

The following example illustrates getting and setting secondary indexes.

%% Create new object
13> Obj = riakc_obj:new(<<"test">>, <<"2i_1">>, <<"John Robert Doe, 25">>).
{riakc_obj,<<"test">>,<<"2i_1">>,undefined,[],undefined,
       <<"John Robert Doe, 25">>}
14> MD1 = riakc_obj:get_update_metadata(Obj).
{dict,0,16,16,8,80,48,
  {[],[],[],[],[],[],[],[],[],[],[],[],[],[],[],[]},
  {{[],[],[],[],[],[],[],[],[],[],[],[],[],[],[],[]}}}
15> MD2 = riakc_obj:set_secondary_index(MD1, [{{integer_index, "age"}, [25]},{{binary_index, "name"}, [<<"John">>,<<"Doe">>]}]).
{dict,1,16,16,8,80,48,
  {[],[],[],[],[],[],[],[],[],[],[],[],[],[],[],[]},
  {{[],[],[],[],[],[],[],[],[],[],[],
    [[<<"index">>,
      {<<"name_bin">>,<<"Doe">>},
      {<<"name_bin">>,<<"John">>},
      {<<"age_"...>>,<<...>>}]],
    [],[],[],[]}}}
16> riakc_obj:get_secondary_index(MD2, {binary_index, "name"}).
[<<"Doe">>,<<"John">>]
17> MD3 = riakc_obj:add_secondary_index(MD2, [{{binary_index, "name"}, [<<"Robert">>]}]).
{dict,1,16,16,8,80,48,
  {[],[],[],[],[],[],[],[],[],[],[],[],[],[],[],[]},
  {{[],[],[],[],[],[],[],[],[],[],[],
    [[<<"index">>,
      {<<"name_bin">>,<<"Doe">>},
      {<<"name_bin">>,<<"John">>},
      {<<"age_"...>>,<<...>>},
      {<<...>>,...}]],
    [],[],[],[]}}}
18> riakc_obj:get_secondary_indexes(MD3).
[{{binary_index,"name"},[<<"Doe">>,<<"John">>,<<"Robert">>]},{{integer_index,"age"},[25]}]
19> Obj2 = riakc_obj:update_metadata(Obj,MD3).
{riakc_obj,<<"test">>,<<"2i_1">>,undefined,[],
       {dict,1,16,16,8,80,48,
             {[],[],[],[],[],[],[],[],[],[],[],[],[],[],[],...},
             {{[],[],[],[],[],[],[],[],[],[],[],[[...]],[],...}}},
       <<"John Robert Doe, 25">>}
20> riakc_pb_socket:put(Pid, Obj2).

In order to query based on secondary indexes, the riakc_pb_socket:get_index/4, riakc_pb_socket:get_index/5, riakc_pb_socket:get_index/6 and riakc_pb_socket:get_index/7 functions can be used. These functions also allows secondary indexes to be specified using the tuple described above.

The following example illustrates how to perform exact match as well as range queries based on the record and associated indexes created above.

21> riakc_pb_socket:get_index(Pid, <<"test">>, {binary_index, "name"}, <<"John">>).
{ok,[<<"2i_1">>]}
22> riakc_pb_socket:get_index(Pid, <<"test">>, {integer_index, "age"}, 20, 30).
{ok,[<<"2i_1">>]}

Riak Data Types

Riak Data Types can only be used in buckets of a bucket type in which the datatype bucket property is set to either counter, set, or map.

All Data Types in the Erlang client can be created and modified at will prior to being stored. Basic CRUD operations are performed by functions in riakc_pb_socket specific to Data Types, e.g. fetch_type/3,4 instead of get/3,4,5 for normal objects, update_type/4,5 instead of put/2,3,4, etc.

The current value of a Data Type on the client side is known as a "dirty value" and can be found using the dirty_value/1 function specific to each Data Type, e.g. riakc_counter:dirty_value/1 or riakc_set:dirty_value/1. Fetching the current value from Riak involves the riakc_pb_socket:fetch_type/3,4 function applied to the Data Type's bucket type/bucket/key location.

Counters

Like all Data Types in the Erlang client, counters can be created and incremented/decremented before they are stored in a bucket type/bucket/key location.

Counter = riakc_counter:new().

Counters can be incremented or decremented by any integer amount:

Counter1 = riakc_counter:increment(10, Counter),
riakc_counter:dirty_value(Counter1).
%% 10

The following would store Counter1 under the key page_visits in the bucket users (which bears the type counter_bucket):

riakc_pb_socket:update_type(Pid,
                            {<<"counter_bucket">>, <<"users">>},
                            <<"page_visits">>,
                            riakc_counter:to_op(Counter1)).

The to_op function transforms any Riak Data Type into the necessary set of operations required to successfully update the value in Riak.

Retrieving the counter:

{ok, Counter2} = riakc_pb_socket:fetch_type(Pid,
                                 {<<"counter_bucket">>, <<"users">>},
                                 <<"page_visits">>).

Sets

Like counters, sets can be created and have members added/subtracted prior to storing them:

Set = riakc_set:new(),
Set1 = riakc_set:add_element(<<"foo">>, Set),
Set2 = riakc_set:add_element(<<"bar">>, Set1),
Set3 = riakc_set:del_element(<<"foo">>, Set2),
Set4 = riakc_set:add_element(<<"baz">>, Set3),
riakc_set:dirty_value(Set4).
%% [<<"bar">>, <<"baz">>]

Once client-side updates are completed, updating sets in Riak works just like updating counters:

riakc_pb_socket:update_type(Pid,
                            {<<"set_bucket">>, <<"all_my_sets">>},
                            <<"odds_and_ends">>,
                            riakc_set:to_op(Set4)).

Now, a set with the elements bar and baz will be stored in /types/set_bucket/buckets/all_my_sets/keys/odds_and_ends.

The functions size/1, is_element/2, and fold/3 will work only on values stored in and retrieved from Riak. Any local modifications, including initial values when an object is created, will not be considered.

riakc_set:is_element(<<"bar">>, Set4).
%% false

Maps

Maps are somewhat trickier because maps can contain any number of fields, each of which itself holds one of the five available Data Types: counters, sets, registers, flags, or even other maps.

Like the other Data Types, you can start with a new map on the client side prior to storing the map in Riak:

Map = riakc_map:new().

Updating maps involves both specifying the map field that you wish to update (by both name and Data Type) and then specifying which transformation you wish to apply to that field. Let's say that you want to add a register reg with the value foo to the map Map created above, using an anonymous function:

Map1 = riakc_map:update({<<"reg">>, register},
                        fun(R) -> riakc_register:set(<<"foo">>, R) end,
                        Map).

For more detailed instructions on maps, see the documentation.

Links

Links are also stored in the object metadata dictionary, and can be manipulated by using the get_links/2, get_all_links/1, clear_links/1, delete_links/2, set_link/2 and add_link/2 functions. When using these functions, a link is identified by a tag, and may therefore contain multiple record IDs.

These functions act upon the dictionary returned by the get_metadata/1, get_metadatas/1 and get_update_metadata/1 functions.

The following example illustrates setting and getting links.

%% Create new object
10> Obj = riakc_obj:new(<<"person">>, <<"sarah">>, <<"Sarah, 30">>).
{riakc_obj,<<"person">>,<<"sarah">>,undefined,[],undefined,
       <<"Sarah, 30">>}
11> MD1 = riakc_obj:get_update_metadata(Obj).
{dict,0,16,16,8,80,48,
  {[],[],[],[],[],[],[],[],[],[],[],[],[],[],[],[]},
  {{[],[],[],[],[],[],[],[],[],[],[],[],[],[],[],[]}}}
12> riakc_obj:get_all_links(MD1).
[]
13> MD2 = riakc_obj:set_link(MD1, [{<<"friend">>, [{<<"person">>,<<"jane">>},{<<"person">>,<<"richard">>}]}]).
{dict,1,16,16,8,80,48,
  {[],[],[],[],[],[],[],[],[],[],[],[],[],[],[],[]},
  {{[],[],
    [[<<"Links">>,
      {{<<"person">>,<<"jane">>},<<"friend">>},
      {{<<"person">>,<<"richard">>},<<"friend">>}]],
    [],[],[],[],[],[],[],[],[],[],[],[],[]}}}
14> MD3 = riakc_obj:add_link(MD2, [{<<"sibling">>, [{<<"person">>,<<"mark">>}]}]).
{dict,1,16,16,8,80,48,
  {[],[],[],[],[],[],[],[],[],[],[],[],[],[],[],[]},
  {{[],[],
    [[<<"Links">>,
      {{<<"person">>,<<"jane">>},<<"friend">>},
      {{<<"person">>,<<"richard">>},<<"friend">>},
      {{<<"person">>,<<"mark">>},<<"sibling">>}]],
    [],[],[],[],[],[],[],[],[],[],[],[],[]}}}
15> riakc_obj:get_all_links(MD3).
[{<<"friend">>,
    [{<<"person">>,<<"jane">>},{<<"person">>,<<"richard">>}]},
     {<<"sibling">>,[{<<"person">>,<<"mark">>}]}]
16> Obj2 = riakc_obj:update_metadata(Obj,MD3).
{riakc_obj,<<"person">>,<<"sarah">>,undefined,[],
       {dict,1,16,16,8,80,48,
             {[],[],[],[],[],[],[],[],[],[],[],[],[],[],[],...},
             {{[],[],
               [[<<"Links">>,
                 {{<<"person">>,<<"jane">>},<<"friend">>},
                 {{<<"person">>,<<"richard">>},<<"friend">>},
                 {{<<"person">>,<<"mark">>},<<"sibling">>}]],
               [],[],[],[],[],[],[],[],[],[],...}}},
       <<"Sarah, 30">>}
17> riakc_pb_socket:put(Pid, Obj2).
ok

MapReduce

MapReduce jobs can be executed using the riakc_pb_socket:mapred function. This takes an input specification as well as a list of mapreduce phase specifications as arguments. It also allows a non-default timeout to be specified if required.

The function riakc_pb_socket:mapred uses riakc_pb_socket:mapred_stream under the hood, and if results need to be processed as they are streamed to the client, this function can be used instead. The implementation of riakc_pb_socket:mapred provides a good example of how to implement this.

It is possible to define a wide range of inputs for a mapreduce job. Some examples are given below:

Bucket/Key list: [{<<"bucket1">>,<<"key1">>},{<<"bucket1">>,<<"key2">>}]

All keys in a bucket: <<"bucket1">>

Result of exact secondary index match: {index, <<"bucket1">>, {binary_index, "idx"}, <<"key">>}, {index, <<"bucket1">>, <<"idx_bin">>, <<"key">>}

Result of secondary index range query: {index, <<"bucket1">>, {integer_index, "idx"}, 1, 100}, {index, <<"bucket1">>, <<"idx_int">>, <<"1">>, <<"100">>}

The query is given as a list of map, reduce and link phases. Map and reduce phases are each expressed as tuples in the following form:

{Type, FunTerm, Arg, Keep}

Type is an atom, either map or reduce. Arg is a static argument (any Erlang term) to pass to each execution of the phase. Keep is either true or false and determines whether results from the phase will be included in the final value of the query. Riak assumes the final phase will return results.

FunTerm is a reference to the function that the phase will execute and takes any of the following forms:

{modfun, Module, Function} where Module and Function are atoms that name an Erlang function in a specific module.

{qfun,Fun} where Fun is a callable fun term (closure or anonymous function).

{jsfun,Name} where Name is a binary that, when evaluated in Javascript, points to a built-in Javascript function.

{jsanon, Source} where Source is a binary that, when evaluated in Javascript is an anonymous function.

{jsanon, {Bucket, Key}} where the object at {Bucket, Key} contains the source for an anonymous Javascript function.

Below are a few examples of different types of mapreduce queries. These assume that the following test data has been created:

Test Data

Create two test records in the <<"mr">> bucket with secondary indexes and a link as follows:

12> O1 = riakc_obj:new(<<"mr">>, <<"bob">>, <<"Bob, 26">>).
13> M0 = dict:new().
14> M1 = riakc_obj:set_secondary_index(M0, {{integer_index,"age"}, [26]}).
15> O2 = riakc_obj:update_metadata(O1, M1).
16> riakc_pb_socket:put(Pid, O2).
17> O3 = riakc_obj:new(<<"mr">>, <<"john">>, <<"John, 23">>).
18> M2 = riakc_obj:set_secondary_index(M0, {{integer_index,"age"}, [23]}).
19> M3 = riakc_obj:set_link(M2, [{<<"friend">>, [{<<"mr">>,<<"bob">>}]}]).
20> O4 = riakc_obj:update_metadata(O3, M3).
21> riakc_pb_socket:put(Pid, O4).

Example 1: Link Walk

Get all friends linked to john in the mr bucket.

6> {ok, [{N1, R1}]} = riakc_pb_socket:mapred(Pid,[{<<"mr">>, <<"john">>}],[{link, <<"mr">>, <<"friend">>, true}]).
{ok,[{0,[[<<"mr">>,<<"bob">>,<<"friend">>]]}]}

As expected, the link information for bob is returned.

Example 2: Determine total object size using a qfun

Create a qfun that returns the size of the record and feed this into the existing reduce function riak_kv_mapreduce:reduce_sum to get total size.

6> RecSize = fun(G, _, _) -> [byte_size(riak_object:get_value(G))] end.
#Fun<erl_eval.18.82930912>
7> {ok, [{N2, R2}]} = riakc_pb_socket:mapred(Pid,
            {index, <<"mr">>, {integer_index, "age"}, 20, 30},
            [{map, {qfun, RecSize}, none, false},
             {reduce, {modfun, 'riak_kv_mapreduce', 'reduce_sum'}, none, true}]).
{ok,[{1,[15]}]}

As expected, total size of data is 15 bytes.

Security

If you are using Riak Security, you will need to configure your Riak Erlang client to use SSL when connecting to Riak. The required setup depends on the security source that you choose. A general primer on Riak client security can be found in our official docs.

Regardless of which authentication source you use, your client will need to have access to a certificate authority (CA) shared by your Riak server. You will also need to provide a username that corresponds to the username for the user or role that you have created in Riak.

Let's say that your CA is stored in the /ssl_dir directory and bears the name cacertfile.pem and that you need provide a username of riakuser and a password of rosebud. You can input that information as a list of tuples when you create your process identifier (PID) for further connections to Riak:

CertDir = "/ssl_dir",
SecurityOptions = [
                   {credentials, "riakuser", "rosebud"},
                   {cacertfile, filename:join([CertDir, "cacertfile.pem"])}
                  ],
{ok, Pid} = riakc_pb_socket:start("127.0.0.1", 8087, SecurityOptions).

This setup will suffice for password, PAM and trust based authentication.

If you are using certificate-based authentication, you will also need to specify a cert and keyfile. The example below uses the same connection information from the sample above but also points to a cert called cert.pem and a keyfile called key.pem (both stored in the same /ssl_dir directory as the CA):

CertDir = "/ssl_dir",
SecurityOptions = [
                   {credentials, "riakuser", "rosebud"},
                   {cacertfile, filename:join([CertDir, "cacertfile.pem"])},
                   {certfile, filename:join([CertDir, "cert.pem"])},
                   {keyfile, filename:join([CertDir, "key.pem"])}
                  ],
{ok, Pid} = riakc_pb_socket:start("127.0.0.1", 8087, SecurityOptions).

More detailed information can be found in our official documentation.

Timeseries

Assume the following table definition for the examples.

CREATE TABLE GeoCheckin
(
   myfamily    varchar   not null,
   myseries    varchar   not null,
   time        timestamp not null,
   weather     varchar   not null,
   temperature double,
   PRIMARY KEY (
     (myfamily, myseries, quantum(time, 15, 'm')),
     myfamily, myseries, time
   )
)

Store TS Data

To write data to your table, put the data in a list, and use the riakc_ts:put/3 function. Please ensure the the order of the data is the same as the table definition, and note that each row is a tuple of values corresponding to the columns in the table.

{ok, Pid} = riakc_pb_socket:start_link("myriakdb.host", 10017).
riakc_ts:put(Pid, "GeoCheckin", [{<<"family1">>, <<"series1">>, 1234567, <<"hot">>, 23.5}, {<<"family2">>, <<"series99">>, 1234567, <<"windy">>, 19.8}]).

Query TS Data

To query TS data, simply use riakc_ts:query/2 with a connection and a query string. All parts of a table's Primary Key must be included in the where clause.

{ok, Pid} = riakc_pb_socket:start_link("myriakdb.host", 10017).

riakc_ts:query(Pid, "select * from GeoCheckin where time > 1234560 and time < 1234569 and myfamily = 'family1' and myseries = 'series1'").

riakc_ts:query(Pid, "select weather, temperature from GeoCheckin where time > 1234560 and time < 1234569 and myfamily = 'family1' and myseries = 'series1'").

riakc_ts:query(Pid, "select weather, temperature from GeoCheckin where time > 1234560 and time < 1234569 and myfamily = 'family1' and myseries = 'series1' and temperature > 27.0").

Troubleshooting

If start/2 or start_link/2 return {error,econnrefused} the client could not connect to the server - make sure the protocol buffers interface is enabled on the server and the address/port is correct.

Contributors

This is not a comprehensive list, please see the commit history.

riak-erlang-client's People

Contributors

alexmoore avatar andreashasse avatar argv0 avatar beerriot avatar bkerley avatar borshop avatar coderoshi avatar dizzyd avatar dreverri avatar engelsanchez avatar evanmcc avatar fadushin avatar fenek avatar hmmr avatar jtuple avatar kellymclaughlin avatar kuenishi avatar lukebakken avatar macintux avatar martinsumner avatar nickelization avatar reiddraper avatar russelldb avatar rustyio avatar rzezeski avatar seancribbs avatar slfritchie avatar vagabond avatar varnerac avatar zeeshanlakhani avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

riak-erlang-client's Issues

Provide Pre-built Packages of the riak-erlang-client

Would it be possible to provide pre-built versions of the riak-erlang-client as an option for developers who either can not or do not want to build the client from source, eg: an organization that does not have access to github.

Some concerns that we'd discussed internally are the number of possible versions of Erlang you might have to create packages to support.

Another possible option would be to provide a source tarball with all of the dependencies, so that it could be built against the developer's Erlang environment without requiring additional dependency fetching.

Bad content_type() spec

In riakc_obj.erl content_type() is defined as being a string() when in fact it should be a string() | binary().

Incompatible with Erlang r17

riakc fails with Erlang r17 due to the redefinition of the new map() type. Errors received:

/deps/riakc/src/riakc_map.erl:101: type map() undefined
/deps/riakc/src/riakc_map.erl:102: type map is a new builtin type; its (re)definition is allowed only until the next release

Errors cause compilation fail

dialyzer: Could not get abstract code for file riak_kv_pb.beam

When trying to build a PLT, I get the following error:

dialyzer: Could not get abstract code for file: deps/riakc/deps/riak_pb/ebin/riak_kv_pb.beam (please recompile it with +debug_info)

This is easily fixed by updating the bundled rebar version.

I'm not sure if I can just drop in my locally bootstrapped version of rebar and have it work everywhere? If this is the case, I'll happily submit a PR 😄

Protobuf error on map update

User morricone in the Riak IRC channel reported new failures with fairly straightforward map updates. I've been able to reproduce the problem against a very recent build of Riak 2.0 with the latest version of riak-erlang-client, but not with an older version of same (3bf7da366fc7a5111e9a7d35144fe53d94ad334f).

  riak_pool:with_connection(main_cluster,
    fun(Con) ->
        M2 =
          riakc_map:update({<<"y">>, counter},
            fun(Counter) ->
              riakc_counter:increment(1, Counter)
            end,
            riakc_map:new()),
      Op = riakc_map:to_op(M2),
      riakc_pb_socket:update_type(Con, {<<"maps">>, <<"sdf">>}, <<"asd">>, Op)
    end).

[{protobuffs,decode_value,
             [<<2,8,2>>,2,mapfield_mapfieldtype],
             [{file,"src/protobuffs.erl"},{line,356}]},
 {protobuffs,decode,2,
             [{file,"src/protobuffs.erl"},{line,217}]},
 {riak_dt_pb,decode,3,
             [{file,"src/riak_dt_pb.erl"},{line,157}]},
 {riak_dt_pb,decode,2,
             [{file,"src/riak_dt_pb.erl"},{line,137}]},
 {riak_dt_pb,decode,3,
             [{file,"src/riak_dt_pb.erl"},{line,149}]},
 {riak_dt_pb,decode,2,
             [{file,"src/riak_dt_pb.erl"},{line,137}]},
 {riak_dt_pb,decode,3,
             [{file,"src/riak_dt_pb.erl"},{line,149}]},
 {riak_dt_pb,decode,2,
             [{file,"src/riak_dt_pb.erl"},{line,137}]}]

And:

  riak_pool:with_connection(main_cluster,
    fun(Con) ->
      M2 = riakc_map:update({<<"x">>, map},
        fun(Map) ->
          riakc_map:update({<<"y">>, counter},
            fun(Counter) ->
              riakc_counter:increment(1, Counter)
            end,
            Map)
        end,
        riakc_map:new()),
      Op = riakc_map:to_op(M2),
      riakc_pb_socket:update_type(Con, {<<"maps">>, <<"34243234">>}, <<"56546546">>, Op)
    end).

{error,<<"Error processing incoming message: error:function_clause:
[{riak_pb_dt_codec,decode_type,
                   [undefined],
                   [{file,"src/riak_pb_dt_codec.erl"},{line,170}]},
 {riak_pb_dt_codec,decode_type,2,
                   [{file,"src/riak_pb_dt_codec.erl"},{line,165}]},
 {riak_pb_dt_codec,decode_map_field,2,
                   [{file,"src/riak_pb_dt_codec.erl"},{line,102}]},
 {riak_pb_dt_codec,'-decode_map_op/2-lc$^1/1-1-',3,
                   [{file,"src/riak_pb_dt_codec.erl"},{line,402}]},
 {riak_pb_dt_codec,decode_map_op,2,
                   [{file,"src/riak_pb_dt_codec.erl"},{line,402}]},
 {riak_kv_pb_crdt,update_type,3,
                  [{file,"src/riak_kv_pb_crdt.erl"},{line,150}]},
 {riak_api_pb_server,process_message,4,
                     [{file,"src/riak_api_pb_server.erl"},{line,412}]},
 {riak_api_pb_server,connected,2,
                     [{file,"src/riak_api_pb_server.erl"},{line,254}]}]
">>}

Prevent `riakc_set:new/2` when the context is undefined (or patch `to_op`)

At a glance, creating a set from scratch with riakc_set:new(List, undefined) seemed like a reasonable approach, but riakc_set:to_op/1 doesn't anticipate this case, thus making any set created and stored only contain any elements added after the new/2 invocation.

I'd suggest returning an error when context is undefined, or...

It's also possible to make to_op smart enough to handle this scenario; I haven't conducted any more than minimal tests, so it's possible that the combination of new/2 plus the smarter to_op breaks a half dozen other things.

https://gist.github.com/macintux/1f34d0443fdf029161c6

Listing keys from bucket with inactive bucket-type hangs

With fresh build, listing keys hangs until a bucket type is created and activated:

iex(1)> p = :pooler.take_group_member :riak
#PID<0.72.0>
iex(2)> :riakc_pb_socket.list_keys p, {"message", "client/net/dev"}

After creating the bucket-type and activating it works as expected:

# ./riak-admin bucket-type create message '{"props": {"datatype":"map","yz_index":"message","schema":"_yz_default"}}'
# ./riak-admin bucket-type activate message
message has been activated

Using HTTP API gives not found:

# curl http://localhost:8098/types/message/buckets\?buckets\=true
HTTP/1.1 404 Object Not Found
....
`   

update value from encode type "application/x-erlang-binary" to binary [JIRA: CLIENTS-1020]

When update value from encode type "application/x-erlang-binary", eg. integer to binary, may cause decode error because of the encode type is not update to 'undefined' as update to other encode type eg. string "hello".

Code sample:

riak_put(Pid, Bucket, Key, Value, Options, Timeout) ->
  case riakc_pb_socket:get(Pid, Bucket, Key, [head | Options], Timeout) of
    {ok, Obj} when is_binary(Value) ->  % @notice fix bug of riakc
      %% replace old content-type to null ""
       NewObj = riakc_obj:update_value(Obj, Value, ""),
       riakc_pb_socket:put(Pid, NewObj, Options, Timeout);
    {ok, Obj} ->
      NewObj = riakc_obj:update_value(Obj, Value),
      riakc_pb_socket:put(Pid, NewObj, Options, Timeout);
    Error ->
      Error
  end.

On last version 1.3.1 mapreduce return strange results {ok,[]}

After update from 1.2 I cannot get simple map reduce
That code works fine on 1.2:
riakc_pb_socket:mapred(Pid, [{Bucket, Key}],
                                       [{map, {modfun, riak_kv_mapreduce, map_object_value},undefined, true}])
and return {ok,[{0,Result}]} on success. Now I get {ok,[]} on same code. All buckets and keys reference to real entities

Erlang Riak Client Throws Exceptions

Moved from https://issues.basho.com/show_bug.cgi?id=1277, reported by Tom Burdick

I feel like the throw's happening in the riak erlang client are a bit odd. Especially the get_metadata() throwing no_metadata while get_update_metadata() catches and returns a new dict instead.

I feel like get_metadata, and in general the entire client should be returning something like

GoodResult | {error, Reason}

instead of throwing

And get_metadata should return an empty dict instead of throwing if there's no metadata.

Adding untouched types to a new map gives ugly exception

Flags are false by default, so adding one without enabling it is problematic, but this probably isn't the right way to handle it. Same problem occurs with un-incremented counters.

68> Map2 = riakc_map:update({<<"flag">>, flag}, fun(F) -> F end, Map).          
{map,[],
     [{{<<"flag">>,flag},{flag,false,undefined,undefined}}],
     [],undefined}
69> riakc_map:to_op(Map2).
* exception error: no match of right hand side value undefined
    in function  riakc_map:fold_extract_op/3 (src/riakc_map.erl, line 213)
    in call from orddict:fold/3 (orddict.erl, line 194)
    in call from riakc_map:to_op/1 (src/riakc_map.erl, line 123)
70> riakc_map:to_op(riakc_map:new()).
undefined

Inconsistent timeout handling

Most of riakc_pb_socket function return tuple {error, timeout} when the call timeouts. Mapreds, key listings and search however returns {error, {timeout, term()}} in case of timeout. All these information is available only by digging through the source.

It would be nice if timeouts were handled in a consistent way and documented in the types.

UTF-8 in 2i keys encoded "twice"

I've got a weird issue with riakc_pb and UTF-8 strings in 2i keys.
It appears that Riak treats my UTF-8 index key as Latin-1 upon doing a
put. When I read the object back up it looks like it has tried to UTF-8
encode the already UTF-8 encoded string... The following script should
reproduce it on Riak 1.1.2: https://gist.github.com/3076921

No API for secondary indexes

In PB client working with secondary indexes means working with internal data structures that leak into the client code: dict for metadata, list for indexes, <<"index">> as the metadata dict key. It would be nice if adding, removing, deleting, reviewing changes (between current and updated metadata) operations on indexes were hidden behind a riakc_obj API function.

Documentation for get_index() needs to be updated

Documentation states the return for get_index() should be {ok, [ListOfKeys]}. 1.4 version returns {ok, {keys,[ListOfKeys]}}. The docs should be updated so there is no confusion when implementing the riak-erlang-client.

Silent disconnect from node

I have problem with {error, disconnected}. I use hottub as pool and long connections . But when connection disconnected we still get is_connected(Socket) = true. And only if I make request I get {error, disconnected}. I try to write connection checkers, they failed to achieve my simple goal - live connection in the pool.

We try request rerun in case of {error, disconnected} but it does not look like good solution for pools with more than 10 workers.

Can you provide tunning documentation and fix problem with is_connected()

riakc_pb_socket:list_search_indexes/1 gets unknown_response

When a fresh riak-2.0.0pre8 node started up, then

    {ok, C} = riakc_pb_socket:start_link(localhost, 8087),
    {ok, Indexes} = riakc_pb_socket:list_search_indexes(C),

fails with

escript: exception error: no match of right hand side value 
                 {error,
                     {unknown_response,
                         {request,#Ref<0.0.0.77>,
                             {rpbyokozunaindexgetreq,undefined},
                             {<0.2.0>,#Ref<0.0.0.76>},
                             undefined,60000,#Ref<0.0.0.78>},
                         rpbyokozunaindexgetresp}}

As the message says riakc_pb_socket:process_response/3 has no match for this message, just adding a clause

process_response(#request{msg = #rpbyokozunaindexgetreq{}},
                 rpbyokozunaindexgetresp, State) ->
    {reply, {ok, []}, State};

solved the problem. But with a glance at yz_pb_admin.erl it looks returning #rpcyokozunaindexgetresp{index=[]} when a fresh node is up. I have no idea about whether the bug is in, client or yz for now.

another prop_modified bug on jrd-pure-ops

  riakc_datatype:114: datatypes_test_ ( prop_modified(riakc_map) ).................Failed! Reason:
{'EXIT',
    {function_clause,
        [{riakc_register,set,
             [<<181,14>>,<<>>],
             [{file,"src/riakc_register.erl"},{line,95}]},
         {riakc_map,update,3,[{file,"src/riakc_map.erl"},{line,182}]},
         {riakc_datatype,'-prop_modified/1-fun-0-',2,
             [{file,"src/riakc_datatype.erl"},{line,160}]}]}}
After 15 tests.
{{map,[{{<<>>,register},<<>>},{{<<"��">>,counter},4}],[],[],<<>>},
 {update,[{<<>>,register},#Fun<riakc_map.10.126007009>]}}
Shrinking..(2 times)
Reason:
{'EXIT',
    {function_clause,
        [{riakc_register,set,
             [<<>>,<<>>],
             [{file,"src/riakc_register.erl"},{line,95}]},
         {riakc_map,update,3,[{file,"src/riakc_map.erl"},{line,182}]},
         {riakc_datatype,'-prop_modified/1-fun-0-',2,
             [{file,"src/riakc_datatype.erl"},{line,160}]},
         {eqc_lazy_lists,lazy_safe_map,2,
             [{file,"../src/eqc_lazy_lists.erl"},{line,40}]}]}}
{{map,[{{<<>>,register},<<>>}],[],[],<<>>},
 {update,[{<<>>,register},#Fun<riakc_map.10.126007009>]}}
*failed*
in function riakc_datatype:'-datatypes_test_/0-fun-4-'/3 (src/riakc_datatype.erl, line 114)
**error:{assertEqual_failed,[{module,riakc_datatype},
                     {line,114},
                     {expression,"quickcheck ( ? QC_OUT ( eqc : testing_time ( 2 , ? MODULE : Prop ( Mod ) ) ) )"},
                     {expected,true},
                     {value,false}]}

Allow the use of iolists as parameters [JIRA: CLIENTS-858]

update_type/, fetch_type/ and modify_type/5 all allow the use of iolists as bucket name. (Despite type annotations). I found this quite useful for generating bucket names dynamically. This behaviour would also be useful for the riakc_obk functions. Even more so for the value parameters.

I propose to convert most binary parameters to also allow iolists to be passed.

Update Riak Erlang PB client documentation

Moved from https://issues.basho.com/show_bug.cgi?id=1035, reported by @rustyio

When indexing values in the Erlang pb client, the values must be converted to binary first (with term_to_binary/1) and the content type must be "application/x-erlang-binary". This is not clear in the docs.

Also, fields and values must be specified as binaries.

Example of doing it right:

F = fun(DB, Key, Value) ->
            {ok, Pid} = riakc_pb_socket:start_link("127.0.0.1", 8087),
            InsertObject = riakc_obj:new(list_to_binary(DB),
                                         list_to_binary(Key), 
                                         term_to_binary(Value),
                                         "application/x-erlang-binary"),
            riakc_pb_socket:put(Pid, InsertObject),    
            riakc_pb_socket:stop(Pid),
            {ok, insert_successful}
    end.

F("User","name",[{<<"fname">>,<<"Fname">>},{<<"lname">>,<<"Lname">>}]).
search:search("User", "fname:Fname").

EQC-detected bug on jrd-pure-ops

To make sure I don't lose track of this

  riakc_datatype:114: datatypes_test_ ( prop_modified(riakc_map) )....................................................................................................................................................................................................Failed! Reason:
{'EXIT',{{badmatch,undefined},
         [{riakc_map,fold_extract_op,3,
                     [{file,"src/riakc_map.erl"},{line,244}]},
          {orddict,fold,3,[{file,"orddict.erl"},{line,194}]},
          {riakc_map,to_op,1,[{file,"src/riakc_map.erl"},{line,142}]},
          {riakc_datatype,'-prop_modified/1-fun-0-',2,
                          [{file,"src/riakc_datatype.erl"},{line,161}]}]}}
After 194 tests.
{{map,[{{<<31,0,29>>,register},{register,<<133,191,180,142>>,undefined}},
       {{<<"�6�">>,register},{register,<<"���">>,undefined}},
       {{<<"�zNo">>,register},{register,<<"/">>,undefined}}],
      [],[],<<"C">>},
 {update,[{<<"a�/">>,counter},#Fun<riakc_map.10.4738459>]}}
Shrinking...(3 times)
Reason:
{'EXIT',{{badmatch,undefined},
         [{riakc_map,fold_extract_op,3,
                     [{file,"src/riakc_map.erl"},{line,244}]},
          {orddict,fold,3,[{file,"orddict.erl"},{line,194}]},
          {riakc_map,to_op,1,[{file,"src/riakc_map.erl"},{line,142}]},
          {riakc_datatype,'-prop_modified/1-fun-0-',2,
                          [{file,"src/riakc_datatype.erl"},{line,161}]},
          {eqc_lazy_lists,lazy_safe_map,2,
                          [{file,"../src/eqc_lazy_lists.erl"},{line,40}]}]}}
{{map,[],[],[],<<>>},{update,[{<<>>,counter},#Fun<riakc_map.10.4738459>]}}
*failed*
in function riakc_datatype:'-datatypes_test_/0-fun-4-'/3 (src/riakc_datatype.erl, line 114)
**error:{assertEqual_failed,[{module,riakc_datatype},
                     {line,114},
                     {expression,"quickcheck ( ? QC_OUT ( eqc : testing_time ( 2 , ? MODULE : Prop ( Mod ) ) ) )"},
                     {expected,true},
                     {value,false}]}

`dirty_value` is dirtier than necessary

19>     Set = riakc_set:new(),
19>     Set1 = riakc_set:add_element(<<"foo">>, Set),
19>     Set2 = riakc_set:add_element(<<"bar">>, Set1),
19>     Set3 = riakc_set:del_element(<<"foo">>, Set2),
19>     Set4 = riakc_set:add_element(<<"baz">>, Set3),
19>     riakc_set:dirty_value(Set4).
[<<"bar">>,<<"baz">>,<<"foo">>]

fetch_type return value on notfound seems suboptimal

I suspect this may be a holdover from pre-bucket-type days. Currently, if an object isn't found, the return value is {error, {notfound, <type>}}, whereas for normal K/V operations it's {error, notfound}.

Since fetch_type doesn't take a type for each request, and since each bucket type can only work with one data type, returning the type as part of the error code seems unnecessary (and means client code needs to watch for two different patterns).

Inconvenient siblings resolution in reads

When there are conflicting values, riakc_obj:get_value/1 fails with siblings exception. In my client code, I have a sibling resolution function that performs some automatic analysis, chooses one value and stores the resolved object to the DB. So far so good. The problem is I cannot use the chosen sibling as an object to retrieve value from, as the above mentioned call throws the same sibling exception as for the unresolved version. See the following:

6> Pid = pooler:take_member().
<0.98.0>
8> {ok, Obj} = riakc_pb_socket:get(Pid, <<"user">>, <<"TestVCUser1">>).
{ok,{riakc_obj,<<"user">>,<<"TestVCUser1">>,
<<107,206,97,96,96,96,205,96,202,5,82,28,49,27,174,115,5,
120,44,245,202,96,74,...>>,
[{{dict,3,16,16,8,80,48,
{[],[],[],[],[],[],[],[],[],[],[],[],...},
{{[],[],[],[],[],[],[],[],[],[],...}}},
<<131,104,25,100,0,4,117,115,101,114,100,0,9,117,110,100,
101,102,105,...>>},
{{dict,3,16,16,8,80,48,
{[],[],[],[],[],[],[],[],[],[],[],...},
{{[],[],[],[],[],[],[],[],[],...}}},
<<131,104,25,100,0,4,117,115,101,114,100,0,9,117,110,
100,101,102,...>>}],
undefined,undefined}}
9> riakc_obj:value_count(Obj).
2
10> SelectedSibling = riakc_obj:select_sibling(1, Obj).
{riakc_obj,<<"user">>,<<"TestVCUser1">>,
<<107,206,97,96,96,96,205,96,202,5,82,28,49,27,174,115,5,
120,44,245,202,96,74,100,201,...>>,
[{{dict,3,16,16,8,80,48,
{[],[],[],[],[],[],[],[],[],[],[],[],[],[],...},
{{[],[],[],[],[],[],[],[],[],[],[[...]],[...],...}}},
<<131,104,25,100,0,4,117,115,101,114,100,0,9,117,110,100,
101,102,105,110,101,...>>},
{{dict,3,16,16,8,80,48,
{[],[],[],[],[],[],[],[],[],[],[],[],[],...},
{{[],[],[],[],[],[],[],[],[],[],[...],...}}},
<<131,104,25,100,0,4,117,115,101,114,100,0,9,117,110,
100,101,102,105,110,...>>}],
{dict,3,16,16,8,80,48,
{[],[],[],[],[],[],[],[],[],[],[],[],[],[],[],...},
{{[],[],[],[],[],[],[],[],[],[],
[[<<...>>|...]],
[[...]],
[],...}}},
<<131,104,25,100,0,4,117,115,101,114,100,0,9,117,110,100,
101,102,105,110,101,100,...>>}
11> riakc_obj:get_value(SelectedSibling).
** exception throw: siblings
in function riakc_obj:get_value/1 (src/riakc_obj.erl, line 183)
12>

This is unfortunate - my get_value_from_riak_object function doesn't need bucket and object key, so I cannot re-read the value from the DB without changing the function signature and provide these values wherever I need to retrieve value from riak object.

Is this behaviour necessary? Is there a reason the resolved sibling cannot be used for value retrieval? If not, could it be changed so that it doesn't throw the sibling exception?

compile error on ubuntu (precise) / OTP 17.0

Linux drlinux 3.13.0-24-generic #47~precise2-Ubuntu SMP Fri May 2 23:30:46 UTC 2014 x86_64 x86_64 x86_64 GNU/Linux
ERLANG_ROOT: /usr/lib/erlang
/usr/lib/erlang/usr/include/ei.h
/usr/lib/erlang/lib/erl_interface-3.7.16/include/ei.h
# make
==> protobuffs (get-deps)
Pulling meck from {git,"git://github.com/basho/meck.git",{tag,"0.8.1"}}
Cloning into 'meck'...
==> meck (get-deps)
==> meck (compile)
Compiled src/meck_expect.erl
Compiled src/meck.erl
compile: warnings being treated as errors
src/meck_proc.erl:51: type dict/0 is deprecated and will be removed in OTP 18.0; use use dict:dict/0 or preferably dict:dict/2
src/meck_proc.erl:408: type dict/0 is deprecated and will be removed in OTP 18.0; use use dict:dict/0 or preferably dict:dict/2
src/meck_proc.erl:445: type dict/0 is deprecated and will be removed in OTP 18.0; use use dict:dict/0 or preferably dict:dict/2
src/meck_proc.erl:446: type dict/0 is deprecated and will be removed in OTP 18.0; use use dict:dict/0 or preferably dict:dict/2
src/meck_proc.erl:476: type dict/0 is deprecated and will be removed in OTP 18.0; use use dict:dict/0 or preferably dict:dict/2
src/meck_proc.erl:477: type dict/0 is deprecated and will be removed in OTP 18.0; use use dict:dict/0 or preferably dict:dict/2
src/meck_proc.erl:482: type dict/0 is deprecated and will be removed in OTP 18.0; use use dict:dict/0 or preferably dict:dict/2
src/meck_proc.erl:483: type dict/0 is deprecated and will be removed in OTP 18.0; use use dict:dict/0 or preferably dict:dict/2
src/meck_proc.erl:488: type dict/0 is deprecated and will be removed in OTP 18.0; use use dict:dict/0 or preferably dict:dict/2
src/meck_proc.erl:489: type dict/0 is deprecated and will be removed in OTP 18.0; use use dict:dict/0 or preferably dict:dict/2
Compiled src/meck_util.erl
make: *** [compile] Error 1

Disconnected error returned even if queue_if_disconnected is set [JIRA: CLIENTS-980]

If queue_if_disconnected is enabled, requests are queued during disconnected operation, instead of immediately getting {error, disconnected} returned. However, any requests in-flight during the disconnect, still get the error tuple returned, as can be seen here. This makes sense for some streaming requests, like receiving the results of list keys, but for simple put operations, it would be nice if the request was automatically retried once the connection was re-established. Thoughts?

Ping Type Spec

The type spec for riakc_pb_socket:ping/2 function has ok | {error, _} as the return type when in reality the response is pong | {error, _}.

It would be good to either fix the spec to match reality or fix the response to match the spec so dialyzer isn't complaining I've done things wrong when I have

pong = ...:ping(...)

Protocol Buffers interface allows the creation of records with an empty key

Clients using protobufs can create records using an empty string as the key. This can be reproduced with the erlang code below. The HTTP interface falls down on this sort of behavior and gives back a 405 for a store, or bucket properties for a get.

Although both behaviors may be correct, the difference in client interfaces may create confusion for users - both interfaces should agree on basic CRUD operations.

{ok, Pid} = riakc_pb_socket:start_link("127.0.0.1", 10017).

BlankKey = riakc_obj:new(<<"test">>, <<"">>, <<"[1,2,3,4,5]">>).

riakc_pb_socket:put(Pid, BlankKey).

{ok, Fetched} = riakc_pb_socket:get(Pid, <<"test">>, <<"">>).

io:format("Empty String Key Data:~n"), 
io:format(riakc_obj:get_value(Fetched)), 
io:format("~n").

riakc_pb_socket:delete(Pid, <<"test">>, <<"">>).

See also:
basho/riak_kv#512
basho/riak-ruby-client#86
basho/riak-dotnet-client#112
basho/riak-python-client#228

MapReduce qfun does not work: error undef

According the documentation, you can pass a fun in the pair {qfun MyFun} to perform MapReduce operations. I can't get this to work at all on 1.3, using Erlang R16B. No matter how simple I make the fun, the Riak node just returns an error undef.

https://github.com/basho/riak-erlang-client#mapreduce (Example 2)

perform_query(Riak) ->
    F = fun(_Value, _, _) -> [42] end, % distilled down to the bare minimum
    {ok, [{_, Result}]} = riakc_pb_socket:mapred(
            Riak, <<"people">>,
            [{map, {qfun, F}, none, true}]),
    Result.

%% =>
%% error:{badmatch,
%%                            {error,
%%                                <<"{\"phase\":0,\"error\":\"undef\",\"input\":\"{ok,{r_object,<<\\\"people\\\">>,<<\\\"elaine\\\">>,[{r_content,{dict,4,16,16,8,80,48,{[],[],[],[],[],[],[],[],[],[],[],[],[],[],[],[]},{{[],[],[],[],[],[],[],[],[],[],[[<<\\\"content-type\\\">>,97,112,112,108,105,99,97,116,105,111,110,47,120,45,101,114,108,97,110,103,45,98,105,110,97,114,121],[<<\\\"X-Riak-VTag\\\">>,53,85,86,69,89,88,112,67,55,98,88,103,52,104,82,117,49,69,52,74,69,68]],[[<<\\\"index\\\">>]],[],[[<<\\\"X-Riak-Last-Modified\\\">>|{1365,677239,432299}]],[],[]}}},<<131,108,0,0,0,2,104,2,100,0,4,110,97,109,101,...>>}],...},...}\",\"type\":\"error\",\"stack\":\"[{#Fun<homepage_handler.0.107446428>,[{r_object,<<\\\"people\\\">>,<<\\\"elaine\\\">>,[{r_content,{dict,4,16,16,8,80,48,{[],[],[],[],[],[],[],[],[],[],[],[],[],[],[],[]},{{[],[],[],[],[],[],[],[],[],[],[[<<\\\"content-type\\\">>,97,112,112,108,105,99,97,116,105,111,110,47,120,45,101,114,108,97,110,103,45,98,105,110,97,114,121],[<<\\\"X-Riak-VTag\\\">>,53,85,86,69,89,88,112,67,55,98,88,103,52,104,82,117,49,69,52,74,69,68]],[[<<\\\"index\\\">>]],[],[[<<\\\"X-Riak-Last-Modified\\\">>|{1365,677239,432299}]],[],[]}}},<<131,108,...>>}],...},...],...},...]\"}">>}}

Now reading this blog post (http://www.javalimit.com/2010/05/passing-funs-to-other-erlang-nodes.html) it sounds like what riakc has documented in the README cannot ever work, unless my application's .beam files are physically copied to all Riak nodes and put in the code path on those nodes. Obviously this is not practical, especially during the development phase while I need to iterate quickly.

Am I doing this wrong, or does the README just neglect to say that using the Riak client is only possible if you application also lives on the Riak servers itself?

riak_pb_messages:msg_code/1 is undef [JIRA: CLIENTS-57]

Just after cloned and built the master, which is e01cb4b44b81cfc29869 and which seems to be broken.

$ cd riak-erlang-client
$ make
$ ERL_LIBS=deps erl -pa
....
9> f(C), {ok, C} = riakc_pb_socket:start_link(localhost, 8087).
{ok,<0.54.0>}
10> riakc_pb_socket:get(C, <<"b">>, <<"k">>).                   

=ERROR REPORT==== 8-Jan-2014::18:15:31 ===
** Generic server <0.54.0> terminating 
** Last message in was {req,
                           {rpbgetreq,<<"b">>,<<"k">>,undefined,undefined,
                               undefined,undefined,undefined,undefined,
                               undefined,undefined,undefined,undefined,
                               undefined},
                           60000}
** When Server state == {state,localhost,8087,false,false,#Port<0.872>,false,
                               gen_tcp,undefined,
                               {[],[]},
                               1,[],infinity,undefined,undefined,undefined,
                               undefined,[],100}
** Reason for termination == 
** {'module could not be loaded',
       [{riak_pb_messages,msg_code,[rpbgetreq],[]},
        {riak_pb_codec,encoder_for,1,
            [{file,"src/riak_pb_codec.erl"},{line,105}]},
        {riak_pb_codec,encode,1,[{file,"src/riak_pb_codec.erl"},{line,76}]},
        {riakc_pb_socket,encode_request_message,1,
            [{file,"src/riakc_pb_socket.erl"},{line,2092}]},
        {riakc_pb_socket,send_request,2,
            [{file,"src/riakc_pb_socket.erl"},{line,2075}]},
        {riakc_pb_socket,handle_call,3,
            [{file,"src/riakc_pb_socket.erl"},{line,1258}]},
        {gen_server,handle_msg,5,[{file,"gen_server.erl"},{line,585}]},
        {proc_lib,init_p_do_apply,3,[{file,"proc_lib.erl"},{line,239}]}]}
** exception exit: undef
     in function  riak_pb_messages:msg_code/1
        called as riak_pb_messages:msg_code(rpbgetreq)
     in call from riak_pb_codec:encoder_for/1 (src/riak_pb_codec.erl, line 105)
     in call from riak_pb_codec:encode/1 (src/riak_pb_codec.erl, line 76)
     in call from riakc_pb_socket:encode_request_message/1 (src/riakc_pb_socket.erl, line 2092)
     in call from riakc_pb_socket:send_request/2 (src/riakc_pb_socket.erl, line 2075) 
     in call from riakc_pb_socket:handle_call/3 (src/riakc_pb_socket.erl, line 1258)
     in call from gen_server:handle_msg/5 (gen_server.erl, line 585)
     in call from proc_lib:init_p_do_apply/3 (proc_lib.erl, line 239)
11> 

Not a git repository issue

Hi i am using ubuntu 13.04 while installing riak -erlang-client i am facing this issue due to that i am rebar abort

Compiled src/riakc_obj.erl
Compiled src/riakc_pb_socket.erl
ERROR: git describe --always --tags failed with error: 128 and output:
fatal: Not a git repository (or any of the parent directories): .git

Any help will be appreciated thanks

Document 2i usage

Moving this issue over from the Riak Wiki. At some point there will be a bonafide client wiki, and this should be a part of it.

Yet another prop_modified bug, sets, jrd-pure-ops

  riakc_datatype:114: datatypes_test_ ( prop_value_immutable(riakc_map) ).............................................Failed! Reason:
{'EXIT',
    {function_clause,
        [{riakc_set,del_element,
             [<<"A#R9">>,[]],
             [{file,"src/riakc_set.erl"},{line,132}]},
         {riakc_map,update,3,[{file,"src/riakc_map.erl"},{line,182}]},
         {riakc_datatype,'-prop_value_immutable/1-fun-0-',2,
             [{file,"src/riakc_datatype.erl"},{line,146}]}]}}
After 43 tests.
{{map,[{{<<>>,set},[]},
       {{<<134,145,38,42,92>>,counter},-10},
       {{<<168,217,212,152,154>>,flag},true},
       {{<<"�� y">>,flag},false}],
      [],[],
      <<29>>},
 {update,[{<<>>,set},#Fun<riakc_map.10.126007009>]}}
Shrinking....(4 times)
Reason:
{'EXIT',
    {function_clause,
        [{riakc_set,add_element,
             [<<>>,[]],
             [{file,"src/riakc_set.erl"},{line,126}]},
         {riakc_map,update,3,[{file,"src/riakc_map.erl"},{line,182}]},
         {riakc_datatype,'-prop_value_immutable/1-fun-0-',2,
             [{file,"src/riakc_datatype.erl"},{line,146}]},
         {eqc_lazy_lists,lazy_safe_map,2,
             [{file,"../src/eqc_lazy_lists.erl"},{line,40}]}]}}
{{map,[{{<<>>,set},[]}],[],[],<<>>},
 {update,[{<<>>,set},#Fun<riakc_map.10.126007009>]}}
*failed*
in function riakc_datatype:'-datatypes_test_/0-fun-4-'/3 (src/riakc_datatype.erl, line 114)
**error:{assertEqual_failed,[{module,riakc_datatype},
                     {line,114},
                     {expression,"quickcheck ( ? QC_OUT ( eqc : testing_time ( 2 , ? MODULE : Prop ( Mod ) ) ) )"},
                     {expected,true},
                     {value,false}]}

'function not exported' riak_kv_pb

Error when calling: riakc_pb_socket:list_keys(Conn, Bucket).

Gives 'function not exported',
[{riak_kv_pb,decode, ...

Problem after pull-request for riak_pb.

Request timers are started too early

riakc_pb_socket uses {active, once} to receive TCP data as messages. In order to support timeouts on reading this data, it also sends itself messages using erlang:send_after. Since only one request can be outstanding at once, concurrent requests are queued up, and processed FIFO. However, the timer for an individual request is started when the request is queued, not when it is actually sent to Riak. The problem is that we start the timer (send_after) inside of new_request, when this request might just be queued. This has two consequences:

  • The timer may go off during a different request, in which case the queued request is removed.
  • The timer may go off when we've been waiting on TCP data for less than timeout.

This may actually be on purpose, but to me it conflates a TCP read timeout with an 'overall request' timeout, which would include time spent waiting in the queue.

ubuntu14.04 R17 riak2 does not work?

Eshell V6.1 (abort with ^G)
1> {ok, Pid} = riakc_pb_socket:start_link("127.0.0.1", 8087).
{ok,<0.35.0>}
2> riakc_pb_socket:ping(Pid).

=ERROR REPORT==== 7-Jul-2014::11:05:44 ===
** Generic server <0.35.0> terminating
** Last message in was {req,rpbpingreq,60000}
** When Server state == {state,"127.0.0.1",8087,false,false,#Port<0.826>,
false,gen_tcp,undefined,
{[],[]},
1,[],infinity,undefined,undefined,undefined,
undefined,[],100}
** Reason for termination ==
** {'module could not be loaded',
[{riak_pb_codec,encode,[rpbpingreq],[]},
{riakc_pb_socket,encode_request_message,1,
[{file,"src/riakc_pb_socket.erl"},{line,2094}]},
{riakc_pb_socket,send_request,2,
[{file,"src/riakc_pb_socket.erl"},{line,2077}]},
{riakc_pb_socket,handle_call,3,
[{file,"src/riakc_pb_socket.erl"},{line,1258}]},
{gen_server,handle_msg,5,[{file,"gen_server.erl"},{line,580}]},
{proc_lib,init_p_do_apply,3,[{file,"proc_lib.erl"},{line,239}]}]}
** exception exit: undef
in function riak_pb_codec:encode/1
called as riak_pb_codec:encode(rpbpingreq)
in call from riakc_pb_socket:encode_request_message/1 (src/riakc_pb_socket.erl, line 2094)
in call from riakc_pb_socket:send_request/2 (src/riakc_pb_socket.erl, line 2077)
in call from riakc_pb_socket:handle_call/3 (src/riakc_pb_socket.erl, line 1258)
in call from gen_server:handle_msg/5 (gen_server.erl, line 580)
in call from proc_lib:init_p_do_apply/3 (proc_lib.erl, line 239)
3> MyBucket = <<"test">>.
<<"test">>
4> Val1 = 1.
1
5> Obj1 = riakc_obj:new(MyBucket, <<"one">>, Val1).
{riakc_obj,<<"test">>,<<"one">>,undefined,[],undefined,1}
6> riakc_pb_socket:put(Pid, Obj1).
** exception error: undefined function riak_pb_kv_codec:encode_content/1
in function riakc_pb_socket:put/4 (src/riakc_pb_socket.erl, line 329)

Han can i resolve this?

Not existing function called

After the latest pull request (#57) was merged, I get errors when decoding data:

Code that reproduces the errors:

f().
{ok, Pid} = riakc_pb_socket:start_link("127.0.0.1", 8087). %% this works
riakc_pb_socket:ping(Pid). %% this works
Object = riakc_obj:new(<<"groceries">>, <<"mine">>, <<"eggs & bacon">>).
riakc_pb_socket:put(Pid, Object, [{w, 2}, {dw, 1}, return_body]). %% this doesn't work
{error,notfound} = riakc_pb_socket:get(Pid, <<"groceries">>, <<"not_used_key">>). %% this works
riakc_pb_socket:get(Pid, <<"groceries">>, <<"mine">>). %% this doesn't work

This is type of error messages I get:

** Reason for termination ==
** {'function not exported',[{riak_kv_pb,decode,[rpblistkeysresp,.....]},
                            {riakc_pb_socket,handle_info,2},
                            {gen_server,handle_msg,5},
                            {proc_lib,init_p_do_apply,3}]}

Support high-level balancing between multiple nodes like e.g. the Ruby client does

# Automatically balance between multiple nodes
client = Riak::Client.new(:nodes => [
  {:host => '10.0.0.1'},
  {:host => '10.0.0.2', :pb_port => 1234},
  {:host => '10.0.0.3', :pb_port => 5678}
])

Using this in the Ruby client (which is quite smart about which node to use and how to deal with failures), would love to have that in the Erlang client too.

Indexes are always strings, regardless of index type

{ok,C}=riakc_pb_socket:start_link("localhost",8087).
O1=riakc_obj:new(<<"B">>,<<"K">>,<<1>>).
M=dict:store(<<"index">>,[{"field_int",1000}]).
O2j=riakc_obj:update_metadata(O1,M).
riakc_pb_socket:put(C,O2,[]).
{ok,O3}=riakc_pb_socket:get(<<"B">>,<<"K">>).
M2=riakc_obj:get_metadata(O3).
Indexes=dict:fetch(<<"index">>,M2).
N=proplists:get_value("field_int",Indexes).
io:format("~p",[N]).

As you can see, going in, N is an integer (1000), and coming out it's a string ("1000"). This is due to:

%% Convert {K,V} tuple to protocol buffers
pbify_rpbpair({K,V}) ->
    #rpbpair{key = K, value = any_to_list(V)}.

Simply put, indexes and links need overhauled in the riak-erlang-client so that people aren't mucking with the metadata dictionary and they keep their types (or error if the wrong type).

Guard against non-string values of content-type in riak-erlang-client

Moved from https://issues.basho.com/show_bug.cgi?id=461, reported by @dreverri

Currently functions that accept a content-type do not guard against the value type (list, binary, etc.), however, the encoding process only converts strings.

If a user passes a binary value for content-type, the function accepts the value but the content-type is not present when saving and reading the object:

http://gist.github.com/481900

Possible solutions:

  1. Only accept string values for content-type
  2. Convert other types to strings - this may cause confusion for users who set a binary content-type on an object but receive a string content-type on a get for the same object.

Affected functions:

riakc_obj:new/4
riakc_obj:update_content_type/2

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.